ribbon 0.5.0 → 0.6.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.
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  *.lock
2
2
  doc/
3
+ .yardoc/
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --protected --no-private
data/LICENSE.MPL2 ADDED
@@ -0,0 +1,3 @@
1
+ This Source Code Form is subject to the terms of the Mozilla Public
2
+ License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
+ You can obtain one at http://mozilla.org/MPL/2.0/.
data/Rakefile CHANGED
@@ -1,3 +1,3 @@
1
1
  require 'rookie'
2
2
 
3
- Rookie::Tasks.new 'ribbon.gemspec'
3
+ Rookie::Tasks.new('ribbon.gemspec').define_tasks!
data/lib/ribbon.rb CHANGED
@@ -1,83 +1,114 @@
1
- require 'ribbon/version'
2
- require 'ribbon/wrapper'
1
+ %w(core_extensions/basic_object options version wrapper).each do |file|
2
+ require file.prepend 'ribbon/'
3
+ end
3
4
 
4
- # == Ruby Object Notation.
5
- #
6
- # ==== Inspired by JSON and OpenStruct.
5
+ # Ribbons are essentially hashes that use method names as keys.
7
6
  #
8
- # Ribbons are essentially hashes that use method calls as keys. This is done via
9
- # <tt>method_missing</tt>. On top of that, one may still use it as a
10
- # general-purpose hash, since the <tt>[key]</tt> and <tt>[key] = value</tt>
11
- # methods are defined.
7
+ # r = Ribbon.new
8
+ # r.key = :value
12
9
  #
13
- # Ribbons support cascading references seamlessly. If you access a property that
14
- # hasn't been set, a new ribbon is created and returned, allowing you to
15
- # continue your calls:
10
+ # If you access a property that hasn't been set, a new ribbon will be returned.
11
+ # This allows you to easily work with nested structures:
16
12
  #
17
- # r = Ribbon.new
18
13
  # r.a.b.c = 10
19
14
  #
20
15
  # You can also assign properties by passing an argument to the method:
21
16
  #
22
- # r.a.b.c 10
17
+ # r.a.b.c 20
23
18
  #
24
19
  # If you pass a block, the value will be yielded:
25
20
  #
26
- # r.a { |a| a.b { |b| b.c 10 } }
21
+ # r.a do |a|
22
+ # a.b do |b|
23
+ # b.c 30
24
+ # end
25
+ # end
27
26
  #
28
- # If the block passed takes no arguments, it will be <tt>instance_eval</tt>ed on
29
- # the value instead:
27
+ # If the block passed takes no arguments, it will be <tt>instance_eval</tt>uated
28
+ # in the context of the value instead:
30
29
  #
31
- # r.a { b { c 10 } }
30
+ # r.a do
31
+ # b do
32
+ # c 40
33
+ # end
34
+ # end
32
35
  #
33
- # Appending a <tt>!</tt> to the end of the property sets the value and returns
34
- # the receiver:
36
+ # Appending a bang (<tt>!</tt>) to the end of the property sets the value and
37
+ # returns the receiver:
35
38
  #
36
- # r.x!(10).y!(20).z!(30) # Equivalent to: r.x = 10; r.y = 20; r.z = 30
39
+ # Ribbon.new.x!(10).y!(20).z!(30)
37
40
  # => {x: 10, y: 20, z: 30}
38
41
  #
39
- # Appending a <tt>?</tt> to the end of the property allows you to peek at the
42
+ # Appending a question mark (<tt>?</tt>) to the end of the property returns the
40
43
  # contents of the property without creating a new ribbon if it is missing:
41
44
  #
42
- # r.p?
45
+ # r.unknown_property?
43
46
  # => nil
44
47
  #
45
- # Seamless reference cascade using arbitrary keys are also supported via the
46
- # <tt>[key]</tt> and <tt>[key] = value</tt> operators, which allow you to
47
- # directly manipulate the internal hash:
48
+ # You can use any object as key with the <tt>[]</tt> and <tt>[]=</tt> operators:
48
49
  #
49
- # r[:j][:k][:l]
50
+ # r['/some/path'].entries = []
50
51
  #
51
- # Keep in mind that the <tt>[key]</tt> operator will always create new ribbons
52
- # for missing properties, which is something that may not be desirable; consider
53
- # wrapping the ribbon with a Ribbon::Wrapper in order to have better access to
54
- # the underlying hash.
52
+ # @author Matheus Afonso Martins Moreira
53
+ # @since 0.1.0
54
+ # @see Ribbon::Wrapper
55
55
  class Ribbon < BasicObject
56
56
 
57
- # The internal Hash.
57
+ # The hash used internally.
58
+ #
59
+ # @return [Hash] the hash used by this Ribbon instance to store data
60
+ # @api private
58
61
  def __hash__
59
62
  @hash ||= (::Hash.new &::Ribbon.default_value_proc)
60
63
  end
61
64
 
62
- # Initializes the new ribbon, merging the internal hash with the given one and
63
- # converting all internal objects. See Ribbon::convert_all! for details.
65
+ # Initializes a new ribbon.
66
+ #
67
+ # If given a block, the ribbon will be yielded to it. If the block doesn't
68
+ # take any arguments, it will be evaluated in the context of the ribbon.
69
+ #
70
+ # All objects inside the hash will be converted.
71
+ #
72
+ # @param [#to_hash, Ribbon, Ribbon::Wrapper] hash the hash with the initial
73
+ # values
74
+ # @see CoreExt::BasicObject#__yield_or_eval__
75
+ # @see convert_all!
64
76
  def initialize(hash = {}, &block)
65
- __hash__.merge! hash
66
- if block.arity.zero? then instance_eval &block else block.call self end if block
77
+ __hash__.merge! ::Ribbon.extract_hash_from(hash)
78
+ __yield_or_eval__ &block
67
79
  ::Ribbon.convert_all! self
68
80
  end
69
81
 
70
- # Gets a value by key.
82
+ # Fetches the value associated with the given key.
83
+ #
84
+ # If given a block, the value will be yielded to it. If the block doesn't take
85
+ # any arguments, it will be evaluated in the context of the value.
86
+ #
87
+ # @param key the key which identifies the value
88
+ # @return the value associated with the given key
89
+ # @see CoreExt::BasicObject#__yield_or_eval__
71
90
  def [](key, &block)
72
91
  value = ::Ribbon.convert __hash__[key]
73
- if block.arity.zero? then value.instance_eval &block
74
- else block.call value end if block
92
+ value.__yield_or_eval__ &block
75
93
  self[key] = value
76
94
  end
77
95
 
78
- # Sets a value by key.
79
- def []=(key, value)
80
- __hash__[key] = value
96
+ # Associates the given values with the given key.
97
+ #
98
+ # @param key the key that will identify the values
99
+ # @param values the values that will be associated with the key
100
+ # @example
101
+ # ribbon = Ribbon.new
102
+ #
103
+ # ribbon[:key] = :value
104
+ # ribbon[:key]
105
+ # # => :value
106
+ #
107
+ # ribbon[:key] = :multiple, :values
108
+ # ribbon[:key]
109
+ # # => [:multiple, :values]
110
+ def []=(key, *values)
111
+ __hash__[key] = if values.size == 1 then values.first else values end
81
112
  end
82
113
 
83
114
  # Handles the following cases:
@@ -87,181 +118,278 @@ class Ribbon < BasicObject
87
118
  # ribbon.method &block => ribbon[method, &block]
88
119
  # ribbon.method value, &block => ribbon[method] = value
89
120
  # ribbon[method, &block]
121
+ #
90
122
  # ribbon.method = value => ribbon[method] = value
123
+ #
91
124
  # ribbon.method! value => ribbon[method] = value
92
125
  # self
126
+ # ribbon.method! &block => ribbon[method, &block]
127
+ # self
128
+ # ribbon.method! value, &block => ribbon[method] = value
129
+ # ribbon[method, &block]
130
+ # self
131
+ #
93
132
  # ribbon.method? => ribbon.__hash__.fetch method
94
133
  # ribbon.method? value => ribbon.__hash__.fetch method, value
95
134
  # ribbon.method? &block => ribbon.__hash__.fetch method, &block
135
+ # ribbon.method? value, &block => ribbon.__hash__.fetch method, value, &block
96
136
  def method_missing(method, *args, &block)
97
- m = method.to_s.strip.gsub(/[=?!]$/, '').strip.to_sym
98
- case method.to_s[-1]
99
- when '='
100
- self[m] = args.first
101
- when '!'
102
- self[m] = args.first; self
103
- when '?'
104
- begin self.__hash__.fetch m, *args, &block
137
+ method_string = method.to_s
138
+ key = method_string.strip.gsub(/[=?!]$/, '').strip.intern
139
+ case method_string[-1]
140
+ when ?=
141
+ __send__ :[]=, key, *args
142
+ when ?!
143
+ __send__ :[]=, key, *args unless args.empty?
144
+ self[key, &block]
145
+ self
146
+ when ??
147
+ begin self.__hash__.fetch key, *args, &block
105
148
  rescue ::KeyError; nil end
106
149
  else
107
- self[method] = args.first unless args.empty?
108
- self[method, &block]
150
+ __send__ :[]=, key, *args unless args.empty?
151
+ self[key, &block]
109
152
  end
110
153
  end
111
154
 
112
- # Computes a simple key: value string for easy visualization of this ribbon.
155
+ # Generates a simple <tt>key: value</tt> string representation of this ribbon.
113
156
  #
114
- # In +opts+ can be specified several options that customize how the string
115
- # is generated. Among those options:
116
- #
117
- # [:separator] Used to separate a key/value pair. Default is <tt>': '</tt>.
118
- # [:key] Symbol that will be sent to the key in order to obtain its
119
- # string representation. Defaults to <tt>:to_s</tt>.
120
- # [:value] Symbol that will be sent to the value in order to obtain its
121
- # string representation. Defaults to <tt>:inspect</tt>.
157
+ # @option opts [String] :separator Separates the key/value pair.
158
+ # Default is <tt>': '</tt>.
159
+ # @option opts [Symbol] :key Will be sent to the key in order to convert
160
+ # it to a string. Default is <tt>:to_s</tt>.
161
+ # @option opts [Symbol] :value Will be sent to the value in order to
162
+ # convert it to a string. Default is
163
+ # <tt>:inspect</tt>.
164
+ # @return [String] the string representation of this ribbon
122
165
  def to_s(opts = {})
123
- to_s_recursive opts
166
+ __to_s_recursive__ ::Ribbon.extract_hash_from(opts)
124
167
  end
125
168
 
126
- # Same as #to_s.
127
169
  alias inspect to_s
128
170
 
129
- # The class methods.
130
- class << self
171
+ private
131
172
 
132
- # A Proc which returns a new ribbon as the default value for the given hash
133
- # key.
134
- def default_value_proc
135
- @default_value_proc ||= (proc { |hash, key| hash[key] = Ribbon.new })
136
- end
173
+ # Computes a string value recursively for the given ribbon, and all ribbons
174
+ # inside it, using the given options.
175
+ #
176
+ # @since 0.3.0
177
+ # @see #to_s
178
+ def __to_s_recursive__(opts = {}, ribbon = self)
179
+ ksym = opts.fetch(:key, :to_s).to_sym
180
+ vsym = opts.fetch(:value, :inspect).to_sym
181
+ separator = opts.fetch(:separator, ': ').to_s
182
+ values = ribbon.__hash__.map do |k, v|
183
+ k = k.ribbon if ::Ribbon.wrapped? k
184
+ v = v.ribbon if ::Ribbon.wrapped? v
185
+ k = if ::Ribbon.instance? k then __to_s_recursive__ opts, k else k.__send__ ksym end
186
+ v = if ::Ribbon.instance? v then __to_s_recursive__ opts, v else v.__send__ vsym end
187
+ "#{k}#{separator}#{v}"
188
+ end.join ', '
189
+ "{#{values}}"
190
+ end
137
191
 
138
- # If <tt>object</tt> is a hash, converts it to a ribbon. If it is an array,
139
- # converts any hashes inside.
140
- def convert(object)
141
- case object
142
- when Hash then Ribbon.new object
143
- when Array then object.map { |element| convert element }
144
- else object
145
- end
146
- end
192
+ end
147
193
 
148
- # Converts all values in the given ribbon.
149
- def convert_all!(ribbon)
150
- ribbon.__hash__.each do |key, value|
151
- ribbon[key] = case value
152
- when Ribbon then convert_all! value
153
- else convert value
154
- end
155
- end
156
- ribbon
157
- end
194
+ class << Ribbon
158
195
 
159
- # Merges the hash of +new_ribbon+ with the hash of +old_ribbon+, creating a
160
- # new ribbon in the process.
161
- def merge(old_ribbon, new_ribbon, &block)
162
- old_hash = extract_hash_from old_ribbon
163
- new_hash = extract_hash_from new_ribbon
164
- merged_hash = old_hash.merge new_hash, &block
165
- Ribbon.new merged_hash
166
- end
196
+ alias [] new
167
197
 
168
- # Merges the hash of +new_ribbon+ with the hash of +old_ribbon+, modifying
169
- # +old_ribbon+'s hash in the process.
170
- def merge!(old_ribbon, new_ribbon, &block)
171
- old_hash = extract_hash_from old_ribbon
172
- new_hash = extract_hash_from new_ribbon
173
- old_hash.merge! new_hash, &block
174
- old_ribbon
175
- end
198
+ # Proc used to store a new Ribbon instance as the value of a missing key.
199
+ #
200
+ # @return [Proc] the proc used when constructing new hashes
201
+ # @since 0.4.6
202
+ def default_value_proc
203
+ @default_value_proc ||= (proc { |hash, key| hash[key] = Ribbon.new })
204
+ end
176
205
 
177
- # Merges the +new_ribbon+ and all nested ribbons with the +old_ribbon+
178
- # recursively, returning a new ribbon.
179
- def deep_merge(old_ribbon, new_ribbon, &block)
180
- deep :merge, old_ribbon, new_ribbon, &block
206
+ # Converts hashes to ribbons. Will look inside arrays.
207
+ #
208
+ # @param object the object to convert
209
+ # @return the converted value
210
+ # @since 0.2.0
211
+ def convert(object)
212
+ case object
213
+ when Hash then Ribbon.new object
214
+ when Array then object.map { |element| convert element }
215
+ else object
181
216
  end
217
+ end
182
218
 
183
- # Merges the +new_ribbon+ and all nested ribbons with the +old_ribbon+
184
- # recursively, modifying all ribbons in place.
185
- def deep_merge!(old_ribbon, new_ribbon, &block)
186
- deep :merge!, old_ribbon, new_ribbon, &block
219
+ # Converts all values inside the given ribbon.
220
+ #
221
+ # @param [Ribbon, Ribbon::Wrapper] ribbon the ribbon whose values are to be
222
+ # converted
223
+ # @return [Ribbon, Ribbon::Wrapper] the ribbon with all values converted
224
+ # @since 0.2.0
225
+ # @see convert
226
+ def convert_all!(ribbon)
227
+ ribbon.__hash__.each do |key, value|
228
+ ribbon[key] = case value
229
+ when Ribbon then convert_all! value
230
+ when Ribbon::Wrapper then convert_all! value.ribbon
231
+ else convert value
232
+ end
187
233
  end
234
+ ribbon
235
+ end
188
236
 
189
- # Returns +true+ if the given +object+ is a ribbon.
190
- def instance?(object)
191
- Ribbon === object
192
- end
237
+ # Merges the hashes of the given ribbons.
238
+ #
239
+ # @param [Ribbon, Ribbon::Wrapper, #to_hash] old_ribbon the ribbon with old
240
+ # values
241
+ # @param [Ribbon, Ribbon::Wrapper, #to_hash] new_ribbon the ribbon with new
242
+ # values
243
+ # @return [Ribbon] a new ribbon containing the results of the merge
244
+ # @yieldparam key the key which identifies both values
245
+ # @yieldparam old_value the value from old_ribbon
246
+ # @yieldparam new_value the value from new_ribbon
247
+ # @yieldreturn the object that will be used as the new value
248
+ # @since 0.3.0
249
+ # @see merge!
250
+ # @see extract_hash_from
251
+ def merge(old_ribbon, new_ribbon, &block)
252
+ old_hash = extract_hash_from old_ribbon
253
+ new_hash = extract_hash_from new_ribbon
254
+ merged_hash = old_hash.merge new_hash, &block
255
+ Ribbon.new merged_hash
256
+ end
193
257
 
194
- # Returns +true+ if the given ribbon is wrapped.
195
- def wrapped?(ribbon)
196
- Ribbon::Wrapper === ribbon
197
- end
258
+ # Merges the hashes of the given ribbons in place.
259
+ #
260
+ # @param [Ribbon, Ribbon::Wrapper, #to_hash] old_ribbon the ribbon with old
261
+ # values
262
+ # @param [Ribbon, Ribbon::Wrapper, #to_hash] new_ribbon the ribbon with new
263
+ # values
264
+ # @return [Ribbon, Ribbon::Wrapper, Hash] old_ribbon, which will contain the
265
+ # results of the merge
266
+ # @yieldparam key the key which identifies both values
267
+ # @yieldparam old_value the value from old_ribbon
268
+ # @yieldparam new_value the value from new_ribbon
269
+ # @yieldreturn the object that will be used as the new value
270
+ # @since 0.3.0
271
+ # @see merge
272
+ # @see extract_hash_from
273
+ def merge!(old_ribbon, new_ribbon, &block)
274
+ old_hash = extract_hash_from old_ribbon
275
+ new_hash = extract_hash_from new_ribbon
276
+ old_hash.merge! new_hash, &block
277
+ old_ribbon
278
+ end
198
279
 
199
- # Wraps a ribbon instance in a Ribbon::Wrapper.
200
- def wrap(ribbon = ::Ribbon.new)
201
- Ribbon::Wrapper.new ribbon
202
- end
280
+ # Merges everything inside the given ribbons.
281
+ #
282
+ # @param [Ribbon, Ribbon::Wrapper, #to_hash] old_ribbon the ribbon with old
283
+ # values
284
+ # @param [Ribbon, Ribbon::Wrapper, #to_hash] new_ribbon the ribbon with new
285
+ # values
286
+ # @return [Ribbon] a new ribbon containing the results of the merge
287
+ # @yieldparam key the key which identifies both values
288
+ # @yieldparam old_value the value from old_ribbon
289
+ # @yieldparam new_value the value from new_ribbon
290
+ # @yieldreturn the object that will be used as the new value
291
+ # @since 0.4.5
292
+ # @see merge
293
+ # @see deep_merge!
294
+ # @see extract_hash_from
295
+ def deep_merge(old_ribbon, new_ribbon, &block)
296
+ deep :merge, old_ribbon, new_ribbon, &block
297
+ end
203
298
 
204
- # Unwraps the +ribbon+ if it is wrapped and returns its hash. Returns +nil+
205
- # in any other case.
206
- def extract_hash_from(ribbon)
207
- case ribbon
208
- when Ribbon::Wrapper then ribbon.internal_hash
209
- when Ribbon then ribbon.__hash__
210
- when Hash then ribbon
211
- else nil
212
- end
213
- end
299
+ # Merges everything inside the given ribbons in place.
300
+ #
301
+ # @param [Ribbon, Ribbon::Wrapper, #to_hash] old_ribbon the ribbon with old
302
+ # values
303
+ # @param [Ribbon, Ribbon::Wrapper, #to_hash] new_ribbon the ribbon with new
304
+ # values
305
+ # @return [Ribbon, Ribbon::Wrapper, Hash] old_ribbon, which will contain the
306
+ # results of the merge
307
+ # @yieldparam key the key which identifies both values
308
+ # @yieldparam old_value the value from old_ribbon
309
+ # @yieldparam new_value the value from new_ribbon
310
+ # @yieldreturn the object that will be used as the new value
311
+ # @since 0.4.5
312
+ # @see merge!
313
+ # @see deep_merge
314
+ # @see extract_hash_from
315
+ def deep_merge!(old_ribbon, new_ribbon, &block)
316
+ deep :merge!, old_ribbon, new_ribbon, &block
317
+ end
214
318
 
215
- # Deserializes the hash from the +string+ using YAML and uses it to
216
- # construct a new ribbon.
217
- def from_yaml(string)
218
- Ribbon.new YAML.load(string)
219
- end
319
+ # Tests whether the given object is an instance of Ribbon.
320
+ #
321
+ # @param object the object to be tested
322
+ # @return [true, false] whether the object is an instance of Ribbon
323
+ # @since 0.2.0
324
+ def instance?(object)
325
+ Ribbon === object
326
+ end
220
327
 
221
- # Creates a new instance.
222
- #
223
- # Ribbon[a: :a, b: :b, c: :c]
224
- alias [] new
328
+ # Tests whether the given object is an instance of {Ribbon::Wrapper}.
329
+ #
330
+ # @param object the object to be tested
331
+ # @return [true, false] whether the object is an instance of {Ribbon::Wrapper}
332
+ # @since 0.2.0
333
+ def wrapped?(ribbon)
334
+ Ribbon::Wrapper === ribbon
335
+ end
225
336
 
226
- private
337
+ # Wraps an object in a {Ribbon::Wrapper}.
338
+ #
339
+ # @param [Ribbon, Ribbon::Wrapper, #to_hash] object the object to be wrapped
340
+ # @return [Ribbon::Wrapper] a new wrapped ribbon
341
+ # @since 0.2.0
342
+ def wrap(object = ::Ribbon.new)
343
+ Ribbon::Wrapper.new object
344
+ end
227
345
 
228
- # Common logic for deep merge functions. +merge_func+ should be either
229
- # +merge+ or +merge!+, and denotes which function will be used to merge
230
- # recursively. +args+ will be forwarded to the merge function.
231
- #
232
- # If given a block, it will be called with the key, the old value and the
233
- # new value as parameters and its return value will be used. The value of
234
- # the new hash will be used, otherwise.
235
- def deep(merge_method, old_ribbon, new_ribbon, &block)
236
- send merge_method, old_ribbon, new_ribbon do |key, old_value, new_value|
237
- if instance?(old_value) and instance?(new_value)
238
- deep merge_method, old_value, new_value, &block
239
- else
240
- if block.respond_to? :call then block.call key, old_value, new_value
241
- else new_value end
242
- end
243
- end
346
+ # Returns the hash of a Ribbon. Will attempt to convert other objects.
347
+ #
348
+ # @param [Ribbon, Ribbon::Wrapper, #to_hash] parameter the object to convert
349
+ # @return [Hash] the resulting hash
350
+ # @since 0.2.1
351
+ def extract_hash_from(parameter)
352
+ case parameter
353
+ when Ribbon::Wrapper then parameter.internal_hash
354
+ when Ribbon then parameter.__hash__
355
+ else parameter.to_hash
244
356
  end
357
+ end
245
358
 
359
+ # Deserializes the hash from the string using YAML and uses it to construct a
360
+ # new ribbon.
361
+ #
362
+ # @param [String] string a valid YAML string
363
+ # @return [Ribbon] a new Ribbon
364
+ # @since 0.4.7
365
+ def from_yaml(string)
366
+ Ribbon.new YAML.load(string)
246
367
  end
247
368
 
248
369
  private
249
370
 
250
- # Computes a string value recursively for the given ribbon and all ribbons
251
- # inside it. This implementation avoids creating additional ribbon or
252
- # Ribbon::Wrapper objects.
253
- def to_s_recursive(opts, ribbon = self)
254
- ksym = opts.fetch(:key, :to_s).to_sym
255
- vsym = opts.fetch(:value, :inspect).to_sym
256
- separator = opts.fetch(:separator, ': ').to_s
257
- values = ribbon.__hash__.map do |k, v|
258
- k = k.ribbon if ::Ribbon.wrapped? k
259
- v = v.ribbon if ::Ribbon.wrapped? v
260
- k = if ::Ribbon.instance? k then to_s_recursive opts, k else k.send ksym end
261
- v = if ::Ribbon.instance? v then to_s_recursive opts, v else v.send vsym end
262
- "#{k}#{separator}#{v}"
263
- end.join ', '
264
- "{#{values}}"
371
+ # Common logic for deep merge methods. +merge_method+ should be either
372
+ # +:merge+ or +:merge!+, and denotes which method will be used to merge
373
+ # recursively.
374
+ #
375
+ # @yieldparam key the key which identifies both values
376
+ # @yieldparam old_value the value from old_ribbon
377
+ # @yieldparam new_value the value from new_ribbon
378
+ # @yieldreturn the object that will be used as the new value
379
+ # @since 0.4.5
380
+ # @see merge!
381
+ # @see merge
382
+ # @see deep_merge
383
+ # @see deep_merge!
384
+ def deep(merge_method, old_ribbon, new_ribbon, &block)
385
+ send merge_method, old_ribbon, new_ribbon do |key, old_value, new_value|
386
+ if instance?(old_value) and instance?(new_value)
387
+ deep merge_method, old_value, new_value, &block
388
+ else
389
+ if block then block.call key, old_value, new_value
390
+ else new_value end
391
+ end
392
+ end
265
393
  end
266
394
 
267
395
  end