drain 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +6 -2
- data/.travis.yml +10 -0
- data/.yardopts +6 -1
- data/Gemfile +7 -0
- data/LICENSE.txt +1 -1
- data/README.md +11 -5
- data/Rakefile +7 -12
- data/drain.gemspec +15 -5
- data/gemspec.yml +4 -3
- data/lib/dr.rb +1 -0
- data/lib/{drain → dr}/base.rb +0 -0
- data/lib/{drain → dr}/base/bool.rb +0 -0
- data/lib/dr/base/converter.rb +33 -0
- data/lib/{drain → dr}/base/encoding.rb +0 -0
- data/lib/dr/base/eruby.rb +284 -0
- data/lib/{drain → dr}/base/functional.rb +2 -2
- data/lib/dr/base/graph.rb +378 -0
- data/lib/dr/base/utils.rb +28 -0
- data/lib/dr/parse.rb +1 -0
- data/lib/dr/parse/simple_parser.rb +70 -0
- data/lib/{drain → dr}/parse/time_parse.rb +0 -0
- data/lib/dr/ruby_ext.rb +1 -0
- data/lib/dr/ruby_ext/core_ext.rb +7 -0
- data/lib/{drain/ruby_ext/core_ext.rb → dr/ruby_ext/core_modules.rb} +67 -27
- data/lib/{drain → dr}/ruby_ext/meta_ext.rb +57 -30
- data/lib/dr/tools.rb +1 -0
- data/lib/{drain → dr}/tools/gtk.rb +0 -0
- data/lib/dr/version.rb +4 -0
- data/lib/drain.rb +2 -1
- data/test/helper.rb +12 -1
- data/test/test_converter.rb +42 -0
- data/test/test_core_ext.rb +116 -0
- data/test/test_graph.rb +126 -0
- data/test/test_meta.rb +65 -0
- data/test/test_simple_parser.rb +41 -0
- metadata +45 -21
- data/.document +0 -3
- data/lib/drain/base/eruby.rb +0 -28
- data/lib/drain/base/graph.rb +0 -213
- data/lib/drain/parse.rb +0 -5
- data/lib/drain/parse/simple_parser.rb +0 -61
- data/lib/drain/ruby_ext.rb +0 -5
- data/lib/drain/tools.rb +0 -5
- data/lib/drain/tools/git.rb +0 -116
- data/lib/drain/version.rb +0 -4
File without changes
|
data/lib/dr/ruby_ext.rb
ADDED
@@ -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(
|
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
|
-
#
|
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
|
-
|
108
|
-
#
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
149
|
+
bind(obj).call(*args,&b)
|
129
150
|
end
|
130
151
|
end
|
131
152
|
def call(*args,&b)
|
132
|
-
|
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
|
-
|
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
|
-
|
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
|
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(:
|
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(:
|
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
|
-
#
|
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
|
-
#
|
46
|
-
#
|
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(
|
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
|
-
|
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
|
-
#
|
152
|
+
# def x; "x"; end
|
117
153
|
# end
|
118
154
|
# module Enumerable
|
119
|
-
#
|
120
|
-
#
|
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
|
data/lib/dr/tools.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
lib/dr/base.rb
|
File without changes
|
data/lib/dr/version.rb
ADDED
data/lib/drain.rb
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
require '
|
1
|
+
require 'dr.rb'
|
2
|
+
Drain=DR
|
data/test/helper.rb
CHANGED
@@ -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
|