drain 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +6 -2
  3. data/.travis.yml +10 -0
  4. data/.yardopts +6 -1
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +11 -5
  8. data/Rakefile +7 -12
  9. data/drain.gemspec +15 -5
  10. data/gemspec.yml +4 -3
  11. data/lib/dr.rb +1 -0
  12. data/lib/{drain → dr}/base.rb +0 -0
  13. data/lib/{drain → dr}/base/bool.rb +0 -0
  14. data/lib/dr/base/converter.rb +33 -0
  15. data/lib/{drain → dr}/base/encoding.rb +0 -0
  16. data/lib/dr/base/eruby.rb +284 -0
  17. data/lib/{drain → dr}/base/functional.rb +2 -2
  18. data/lib/dr/base/graph.rb +378 -0
  19. data/lib/dr/base/utils.rb +28 -0
  20. data/lib/dr/parse.rb +1 -0
  21. data/lib/dr/parse/simple_parser.rb +70 -0
  22. data/lib/{drain → dr}/parse/time_parse.rb +0 -0
  23. data/lib/dr/ruby_ext.rb +1 -0
  24. data/lib/dr/ruby_ext/core_ext.rb +7 -0
  25. data/lib/{drain/ruby_ext/core_ext.rb → dr/ruby_ext/core_modules.rb} +67 -27
  26. data/lib/{drain → dr}/ruby_ext/meta_ext.rb +57 -30
  27. data/lib/dr/tools.rb +1 -0
  28. data/lib/{drain → dr}/tools/gtk.rb +0 -0
  29. data/lib/dr/version.rb +4 -0
  30. data/lib/drain.rb +2 -1
  31. data/test/helper.rb +12 -1
  32. data/test/test_converter.rb +42 -0
  33. data/test/test_core_ext.rb +116 -0
  34. data/test/test_graph.rb +126 -0
  35. data/test/test_meta.rb +65 -0
  36. data/test/test_simple_parser.rb +41 -0
  37. metadata +45 -21
  38. data/.document +0 -3
  39. data/lib/drain/base/eruby.rb +0 -28
  40. data/lib/drain/base/graph.rb +0 -213
  41. data/lib/drain/parse.rb +0 -5
  42. data/lib/drain/parse/simple_parser.rb +0 -61
  43. data/lib/drain/ruby_ext.rb +0 -5
  44. data/lib/drain/tools.rb +0 -5
  45. data/lib/drain/tools/git.rb +0 -116
  46. data/lib/drain/version.rb +0 -4
File without changes
@@ -0,0 +1 @@
1
+ lib/dr/base.rb
@@ -0,0 +1,7 @@
1
+ require 'dr/ruby_ext/core_modules'
2
+
3
+ #automatically include the CoreExt modules in their class
4
+ DR::CoreExt.constants.each do |c|
5
+ Module.const_get(c).module_eval {include Module.const_get("DR::CoreExt::#{c}")}
6
+ end
7
+ [Hash, Array].each {|m| m.include(Enumerable)} #to reinclude
@@ -9,7 +9,7 @@ module DR
9
9
  default=h[:default]
10
10
  r={}
11
11
  each do |el|
12
- keys=invh.fetch(value,[default])
12
+ keys=invh.fetch(el,[default])
13
13
  keys.each do |key|
14
14
  (r[key]||=[]) << el
15
15
  end
@@ -67,8 +67,10 @@ module DR
67
67
  end
68
68
 
69
69
  #from a hash {key: [values]} produce a hash {value: [keys]}
70
- #there is already Hash#key which does that, but the difference here is
71
- #that we flatten Enumerable values
70
+ #there is already Hash#invert using Hash#key which does that, but the difference here is that we flatten Enumerable values
71
+ #h={ploum: 2, plim: 2, plam: 3}
72
+ #h.invert #=> {2=>:plim, 3=>:plam}
73
+ #h.inverse #=> {2=>[:ploum, :plim], 3=>[:plam]}
72
74
  def inverse
73
75
  r={}
74
76
  each_key do |k|
@@ -96,49 +98,83 @@ module DR
96
98
  def keyed_value(key, sep: "/")
97
99
  r=self.dup
98
100
  return r if key.empty?
99
- key.split(sep).each do |k|
101
+ key.to_s.split(sep).each do |k|
100
102
  k=k.to_sym if r.key?(k.to_sym) && !r.key?(k)
101
103
  r=r[k]
102
104
  end
103
105
  return r
104
106
  end
105
- end
106
107
 
107
- module Proc
108
- # Safely call our block, even if the user passed in something of a
109
- # different arity (lambda case)
110
- def call_block(*args,**opts)
111
- if block.arity >= 0
112
- case block.arity
113
- when 0
114
- block.call(**opts)
115
- else
116
- block.call(args[0...block.arity],**opts)
117
- end
118
- else
119
- block.call(*args,**opts)
108
+ #take a key of the form ploum/plam/plim
109
+ #and return self[:ploum][:plam][:plim]=value
110
+ def set_keyed_value(key,value, sep: "/", symbolize: true)
111
+ r=self
112
+ *keys,last=key.to_s.split(sep)
113
+ keys.each do |k|
114
+ k=k.to_sym if (symbolize || r.key?(k.to_sym)) and !r.key?(k)
115
+ r[k]={} unless r.key?(k)
116
+ r=r[k]
120
117
  end
118
+ last=last.to_sym if symbolize
119
+ r[last]=value
120
+ self
121
+ end
122
+
123
+ #from a hash {foo: [:bar, :baz], bar: [:plum, :qux]},
124
+ #then leaf [:foo] returns [:plum, :qux, :baz]
125
+ def leafs(nodes)
126
+ expanded=[] #prevent loops
127
+ r=nodes.dup
128
+ begin
129
+ s,r=r,r.map do |n|
130
+ if key?(n) && !expanded.include?(n)
131
+ expanded << n
132
+ fetch(n)
133
+ else
134
+ n
135
+ end
136
+ end.flatten
137
+ end until s==r
138
+ r
121
139
  end
122
140
  end
123
141
 
124
142
  module UnboundMethod
125
143
  #this should be in the stdlib...
144
+ #Note: this is similar to Symbol#to_proc which works like this:
145
+ # foo=:foo.to_proc; foo.call(obj,*args) #=> obj.method(:foo).call(*args)
146
+ # => :length.to_proc.call("foo") #=> 3
126
147
  def to_proc
127
148
  return lambda do |obj,*args,&b|
128
- self.bind(obj).call(*args,&b)
149
+ bind(obj).call(*args,&b)
129
150
  end
130
151
  end
131
152
  def call(*args,&b)
132
- self.to_proc.call(*args,&b)
153
+ to_proc.call(*args,&b)
133
154
  end
134
155
  end
135
156
 
136
157
  module Proc
158
+ # Safely call our block, even if the user passed in something of a
159
+ # different arity (lambda case)
160
+ def call_block(*args,**opts)
161
+ if arity >= 0
162
+ case arity
163
+ when 0
164
+ call(**opts)
165
+ else
166
+ call(args[0...arity],**opts)
167
+ end
168
+ else
169
+ call(*args,**opts)
170
+ end
171
+ end
172
+
137
173
  #similar to curry, but pass the provided arguments on the right
138
174
  #(a difference to Proc#curry is that we pass the argument directly, not
139
175
  #via .call)
140
176
  def rcurry(*args,&b)
141
- return Proc.new do |*a,&b|
177
+ return ::Proc.new do |*a,&b|
142
178
  self.call(*a,*args,&b)
143
179
  end
144
180
  end
@@ -147,7 +183,7 @@ module DR
147
183
  #f.compose(g).(5,6)
148
184
  def compose(g)
149
185
  lambda do |*a,&b|
150
- self.call(g.call(*a,&b))
186
+ self.call(*g.call(*a,&b))
151
187
  end
152
188
  end
153
189
 
@@ -183,6 +219,14 @@ module DR
183
219
  end
184
220
  end
185
221
 
222
+ module CoreRef
223
+ CoreExt.constants.select {|c| const_get("::#{c}").is_a?(Class)}.each do |c|
224
+ refine const_get("::#{c}") do
225
+ include CoreExt.const_get(c)
226
+ end
227
+ end
228
+ end
229
+
186
230
  module Recursive
187
231
  extend self
188
232
  def recursive_constructor(klass)
@@ -194,10 +238,6 @@ module DR
194
238
  end
195
239
  end
196
240
  RecursiveHash=Recursive.recursive_constructor(Hash)
241
+ #For an individual hash: hash = Hash.new {|h,k| h[k] = h.class.new(&h.default_proc) }
197
242
  #Arrays don't accept blocks in the same way as Hashs, we need to pass a length parameter, so we can't use DR::RecursiveHash(Array)
198
243
  end
199
-
200
- #automatically include the *Ext modules in their class
201
- DR::CoreExt.constants.each do |c|
202
- Module.const_get(c).module_eval {include Module.const_get("DR::CoreExt::#{c}")}
203
- end
@@ -2,17 +2,28 @@ module DR
2
2
  module Meta
3
3
  extend self
4
4
  #from http://stackoverflow.com/questions/18551058/better-way-to-turn-a-ruby-class-into-a-module-than-using-refinements
5
+ #See also http://stackoverflow.com/questions/28649472/ruby-refinements-subtleties
6
+ #
5
7
  #convert a class into a module using refinements
6
8
  #ex: (Class.new { include Meta.refined_module(String) { def length; super+5; end } }).new("foo").length #=> 8
7
- def refined_module(klass)
9
+ #This uses the fact that a refining module of klass behaves as if it had
10
+ #klass has his direct ancestor
11
+ def refined_module(klass,&b)
8
12
  klass=klass.singleton_class unless Module===klass
9
13
  Module.new do
14
+ #including the module rather than just returning it allow us to
15
+ #still be able to use 'using' ('using' does not work directly on
16
+ #refining modules only on the enclosing ones)
10
17
  include refine(klass) {
11
- yield if block_given?
18
+ module_eval(&b) if block_given?
12
19
  }
13
20
  end
14
21
  end
15
22
 
23
+ #find the ancestors of obj, its singleton class, its
24
+ #singleton_singleton_class. To avoid going to infinity, we only add a
25
+ #singleton_class when its ancestors contains new modules we have not
26
+ #seen.
16
27
  def all_ancestors(obj)
17
28
  obj=obj.singleton_class unless Module===obj
18
29
  found=[]
@@ -28,25 +39,24 @@ module DR
28
39
  return found
29
40
  end
30
41
 
31
- #add extend_ancestors and extend_complete to Object
42
+ # add extend_ancestors and full_extend to Object
32
43
  def extend_object
33
44
  include_ancestors=Meta.method(:include_ancestors)
34
- include_complete=Meta.method(:include_complete)
45
+ include_complete=Meta.method(:full_include)
35
46
  Object.define_method(:extend_ancestors) do |m|
36
47
  include_ancestors.bind(singleton_class).call(m)
37
48
  end
38
- Object.define_method(:extend_complete) do |m|
49
+ Object.define_method(:full_extend) do |m|
39
50
  include_complete.bind(singleton_class).call(m)
40
51
  end
41
52
  end
42
53
 
43
- #If we don't want to extend a module with Meta, we can still do
54
+ #apply is a 'useless' wrapper to .call, but it also works for UnboundMethod.
55
+ # See also dr/core_ext that adds 'UnboundMethod#call'
56
+ #=> If we don't want to extend a module with Meta, we can still do
44
57
  #Meta.apply(String,method: Meta.instance_method(:include_ancestors),to: self)
45
- #Note: another way is to use Symbold#to_proc which works like this:
46
- #foo=:foo.to_proc; foo.call(obj,*args) #=> obj.method(:foo).call(*args)
47
- #essentially apply is a 'useless' wrapper to .call, but it also works
48
- #for UnboundMethod. See also dr/core_ext that add
49
- #'UnboundMethod#call'
58
+ #(note that in 'Meta.apply', the default option to 'to:' is self=Meta,
59
+ #that's why we need to put 'to: self' again)
50
60
  def apply(*args,method: nil, to: self, **opts,&block)
51
61
  #note, in to self is Meta, except if we include it in another
52
62
  #module so that it would make sense
@@ -55,6 +65,7 @@ module DR
55
65
  when UnboundMethod
56
66
  method=method.bind(to)
57
67
  end
68
+ #We cannot call **opts if opts is empty in case of an empty args, cf https://bugs.ruby-lang.org/issues/10708
58
69
  if opts.empty?
59
70
  method.call(*args,&block)
60
71
  else
@@ -68,6 +79,32 @@ module DR
68
79
  obj.singleton_class.send(:remove_method,method_name)
69
80
  method
70
81
  end
82
+
83
+ #Taken from sinatra/base.rb: return an unbound method from a block, with
84
+ #owner the current module
85
+ #Conversely, from a (bound) method, calling to_proc (hence &m) gives a lambda
86
+ #Note: rather than doing
87
+ #m=get_unbound_method('',&block);m.bind(obj).call(args)
88
+ #one could do obj.instance_exec(args,&block)
89
+ def get_unbound_method(method_name, &block)
90
+ define_method(method_name, &block)
91
+ method = instance_method method_name
92
+ remove_method method_name
93
+ method
94
+ end
95
+
96
+ #like get_unbound_method except we pass a strng rather than a block
97
+ def get_unbound_evalmethod(method_name, method_str, args: '')
98
+ module_eval <<-RUBY
99
+ def #{method_name}(#{args})
100
+ #{method_str}
101
+ end
102
+ RUBY
103
+ method = instance_method method_name
104
+ remove_method method_name
105
+ method
106
+ end
107
+
71
108
  end
72
109
 
73
110
  #helping with metaprograming facilities
@@ -96,7 +133,7 @@ module DR
96
133
  end
97
134
 
98
135
  #include_ancestor includes all modules ancestor, so one can do
99
- #singleton_class.include_ancestors(m) to have a fully featured extend
136
+ #singleton_class.include_ancestors(String) to include the Module ancestors of String into the class
100
137
  def include_ancestors(m)
101
138
  ancestors=m.respond_to?(:ancestors) ? m.ancestors : m.singleton_class.ancestors
102
139
  ancestors.reverse.each do |m|
@@ -104,8 +141,7 @@ module DR
104
141
  end
105
142
  end
106
143
 
107
- #include a module and extend its singleton_class (along with its ancestors)
108
- def include_complete(obj)
144
+ def include_all_ancestors(obj)
109
145
  ancestors=Meta.all_ancestors(obj)
110
146
  ancestors.reverse.each do |m|
111
147
  include m if m.class==Module
@@ -113,11 +149,11 @@ module DR
113
149
  end
114
150
 
115
151
  # module Z
116
- # def x; "x"; end
152
+ # def x; "x"; end
117
153
  # end
118
154
  # module Enumerable
119
- # extend MetaModule
120
- # full_include Z
155
+ # extend MetaModule
156
+ # full_include Z
121
157
  # end
122
158
  # Array.new.x => "x"
123
159
  def full_include other
@@ -130,19 +166,6 @@ module DR
130
166
  end
131
167
  end
132
168
 
133
- #Taken from sinatra/base.rb: return an unbound method from a block, with
134
- #owner the current module
135
- #Conversely, from a (bound) method, calling to_proc (hence &m) gives a lambda
136
- #Note: rather than doing
137
- #m=get_unbound_method('',&block);m.bind(obj).call(args)
138
- #one could do obj.instance_exec(args,&block)
139
- def get_unbound_method(method_name, &block)
140
- define_method(method_name, &block)
141
- method = instance_method method_name
142
- remove_method method_name
143
- method
144
- end
145
-
146
169
  #essentially like define_method, but can pass a Method or an UnboundMethod
147
170
  #see also dr/core_ext which add UnboundMethod#to_proc so we could
148
171
  #instead use define_method(name,&method) and it would work
@@ -167,6 +190,10 @@ module DR
167
190
  end
168
191
  end
169
192
 
193
+ #add_methods(method1, method2, ...)
194
+ #add_methods({name1: method1, name2: method2, ...})
195
+ #add_methods(aClass, :method_name1, :method_name2)
196
+ #add_methods(aClass, {new_name1: :method_name1, new_name2: :method_name2})
170
197
  def add_methods(*args)
171
198
  return if args.empty?
172
199
  if Module === args.first
@@ -0,0 +1 @@
1
+ lib/dr/base.rb
File without changes
@@ -0,0 +1,4 @@
1
+ module DR
2
+ # drain version
3
+ VERSION = "0.2.0"
4
+ end
@@ -1 +1,2 @@
1
- require 'drain/version'
1
+ require 'dr.rb'
2
+ Drain=DR
@@ -1,2 +1,13 @@
1
- require 'rubygems'
2
1
  require 'minitest/autorun'
2
+
3
+ ## Uncomment to launch pry on a failure
4
+ #require 'pry-rescue/minitest'
5
+
6
+ begin
7
+ require 'minitest/reporters'
8
+ Minitest::Reporters.use! Minitest::Reporters::DefaultReporter.new
9
+ #Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
10
+ #Minitest::Reporters.use! Minitest::Reporters::ProgressReporter.new
11
+ rescue LoadError => error
12
+ warn "minitest/reporters not found, not changing minitest reporter: #{error}"
13
+ end
@@ -0,0 +1,42 @@
1
+ require 'helper'
2
+ require 'dr/base/converter'
3
+
4
+ describe DR::Converter do
5
+ before do
6
+ klass=Class.new do
7
+ attr_accessor :a, :h
8
+ def initialize(a,h)
9
+ @a=a
10
+ @h=h
11
+ end
12
+ end
13
+ @obj1=klass.new(["foo","bar"],{foo: :bar})
14
+ @obj2=klass.new([@obj1],{})
15
+ @obj3=klass.new([],{@obj1 => @obj2})
16
+ @obj3.a << @obj3
17
+ end
18
+
19
+ it "Output a hash with the attributes" do
20
+ DR::Converter.to_hash(@obj1, methods: [:a,:h]).must_equal({@obj1 => {a: @obj1.a, h: @obj1.h}})
21
+ end
22
+
23
+ it ":compact compress the values when there is only one method" do
24
+ DR::Converter.to_hash(@obj1, methods: [:a,:h], compact: true).must_equal({@obj1 => {a: @obj1.a, h: @obj1.h}})
25
+ DR::Converter.to_hash(@obj1, methods: [:a], compact: true).must_equal({@obj1 => @obj1.a})
26
+ end
27
+
28
+ it ":check checks that the method exists" do
29
+ -> {DR::Converter.to_hash(@obj1, methods: [:none], check: false)}.must_raise NoMethodError
30
+ DR::Converter.to_hash(@obj1, methods: [:none], check: true).must_equal({@obj1 => {}})
31
+ end
32
+
33
+ it "accepts a list" do
34
+ DR::Converter.to_hash([@obj1,@obj2], methods: [:a,:h]).must_equal({@obj1 => {a: @obj1.a, h: @obj1.h}, @obj2 => {a: @obj2.a, h: @obj2.h}})
35
+ end
36
+
37
+ #this test also test that cycles work
38
+ it ":recursive generate the hash on the values" do
39
+ DR::Converter.to_hash(@obj3, methods: [:a,:h], recursive: true).must_equal({@obj1 => {a: @obj1.a, h: @obj1.h}, @obj2 => {a: @obj2.a, h: @obj2.h}, @obj3 => {a: @obj3.a, h: @obj3.h}})
40
+ end
41
+
42
+ end
@@ -0,0 +1,116 @@
1
+ require 'helper'
2
+ require 'dr/ruby_ext/core_modules'
3
+
4
+ module TestCoreExtRefinements
5
+ using DR::CoreRef
6
+ # TODO make refinements work nicely
7
+ end
8
+
9
+ module TestCoreExt
10
+ require 'dr/ruby_ext/core_ext'
11
+ describe DR::CoreExt do
12
+ describe Enumerable do
13
+ it "Can filter enumerable" do
14
+ [1,2,3,4].filter({odd: [1,3], default: :even}).must_equal({:odd=>[1, 3], :even=>[2, 4]})
15
+ end
16
+ end
17
+
18
+ describe Hash do
19
+ it "Implements Hash#deep_merge" do
20
+ h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
21
+ h2 = { x: { y: [7,8,9] }, z: 'xyz' }
22
+ h1.deep_merge(h2).must_equal({x: {y: [7, 8, 9]}, z: "xyz"})
23
+ h2.deep_merge(h1).must_equal({x: {y: [4, 5, 6]}, z: [7, 8, 9]})
24
+ h1.deep_merge(h2) { |key, old, new| Array(old) + Array(new) }.must_equal({:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]})
25
+ end
26
+
27
+ it "Hash#deep_merge merge array when they start with nil" do
28
+ h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
29
+ h2 = { x: { y: [nil, 7,8,9] }, z: 'xyz' }
30
+ h1.deep_merge(h2).must_equal({x: {y: [4,5,6,7, 8, 9]}, z: "xyz"})
31
+ {x: { y: []} }.deep_merge(h2).must_equal({x: {y: [7, 8, 9]}, z: "xyz"})
32
+ {z: "foo"}.deep_merge(h2).must_equal({x: {y: [7, 8, 9]}, z: "xyz"})
33
+ end
34
+
35
+ it "Implements Hash#inverse" do
36
+ h={ploum: 2, plim: 2, plam: 3}
37
+ h.inverse.must_equal({2=>[:ploum, :plim], 3=>[:plam]})
38
+ end
39
+
40
+ it "Implements Hash#keyed_value" do
41
+ h = { x: { y: { z: "foo" } } }
42
+ h.keyed_value("x/y/z").must_equal("foo")
43
+ end
44
+
45
+ it "Implements Hash#set_keyed_value" do
46
+ h = { x: { y: { z: "foo" } } }
47
+ h.set_keyed_value("x/y/z","bar").must_equal({ x: { y: { z: "bar" } } })
48
+ h.set_keyed_value("x/y","bar2").must_equal({ x: { y: "bar2" } })
49
+ h.set_keyed_value("z/y","bar3").must_equal({ x: { y: "bar2" } , z: {y: "bar3"}})
50
+ end
51
+
52
+ it "Implements Hash#leafs" do
53
+ {foo: [:bar, :baz], bar: [:plum, :qux]}.leafs([:foo]).must_equal([:plum, :qux, :baz])
54
+ end
55
+ end
56
+
57
+ describe UnboundMethod do
58
+ it "Can be converted to a proc" do
59
+ m=String.instance_method(:length)
60
+ ["foo", "ploum"].map(&m).must_equal([3,5])
61
+ end
62
+ it "Can call" do
63
+ String.instance_method(:length).call("foo").must_equal(3)
64
+ end
65
+ end
66
+
67
+ describe Proc do
68
+ # fails due to ruby bug on double splat
69
+ # it "call_block does not worry about arity of lambda" do
70
+ # (->(x,y) {x+y}).call_block(1,2,3).must_equal(3)
71
+ # end
72
+
73
+ it "Can do rcurry" do
74
+ l=->(x,y) {"#{x}: #{y}"}
75
+ m=l.rcurry("foo")
76
+ m.call("bar").must_equal("bar: foo")
77
+ end
78
+
79
+ it "Can compose functions" do
80
+ somme=->(x,y) {x+y}
81
+ carre=->(x) {x*x}
82
+ carre.compose(somme).(2,3).must_equal(25)
83
+ end
84
+
85
+ it "Can uncurry functions" do
86
+ (->(x) {->(y) {x+y}}).uncurry.(2,3).must_equal(5)
87
+ (->(x,y) {x+y}).curry.uncurry.(2,3).must_equal(5)
88
+ end
89
+ end
90
+
91
+ describe Array do
92
+ it "Can be converted to proc (providing extra arguments)" do
93
+ ["ploum","plam"].map(&[:+,"foo"]).must_equal(["ploumfoo", "plamfoo"])
94
+ end
95
+ end
96
+
97
+ describe Object do
98
+ it "this can change the object" do
99
+ "foo".this {|s| s.size}.+(1).must_equal(4)
100
+ end
101
+
102
+ it "and_this emulates the Maybe Monad" do
103
+ "foo".and_this {|s| s.size}.must_equal(3)
104
+ assert_nil nil.and_this {|s| s.size}
105
+ end
106
+ end
107
+
108
+ describe DR::RecursiveHash do
109
+ it "Generates keys when needed" do
110
+ h=DR::RecursiveHash.new
111
+ h[:foo][:bar]=3
112
+ h.must_equal({foo: {bar: 3}})
113
+ end
114
+ end
115
+ end
116
+ end