ribbon 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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