drain 0.1.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.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.gitignore +2 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +25 -0
- data/Rakefile +34 -0
- data/drain.gemspec +59 -0
- data/gemspec.yml +13 -0
- data/lib/drain.rb +1 -0
- data/lib/drain/base.rb +5 -0
- data/lib/drain/base/bool.rb +25 -0
- data/lib/drain/base/encoding.rb +43 -0
- data/lib/drain/base/eruby.rb +28 -0
- data/lib/drain/base/functional.rb +41 -0
- data/lib/drain/base/graph.rb +213 -0
- data/lib/drain/parse.rb +5 -0
- data/lib/drain/parse/simple_parser.rb +61 -0
- data/lib/drain/parse/time_parse.rb +71 -0
- data/lib/drain/ruby_ext.rb +5 -0
- data/lib/drain/ruby_ext/core_ext.rb +203 -0
- data/lib/drain/ruby_ext/meta_ext.rb +211 -0
- data/lib/drain/tools.rb +5 -0
- data/lib/drain/tools/git.rb +116 -0
- data/lib/drain/tools/gtk.rb +49 -0
- data/lib/drain/version.rb +4 -0
- data/test/helper.rb +2 -0
- data/test/test_drain.rb +12 -0
- metadata +118 -0
data/lib/drain/parse.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module DR
|
2
|
+
#utilities to parse some strings into name values
|
3
|
+
module SimpleParser
|
4
|
+
extend self
|
5
|
+
|
6
|
+
#takes a string 'name:value' and return name and value
|
7
|
+
#can specify a default value; if the default is true we match
|
8
|
+
#no-name as name:false
|
9
|
+
def parse_namevalue(nameval, sep: ':', default: nil)
|
10
|
+
name,*val=nameval.split(sep)
|
11
|
+
if val.empty?
|
12
|
+
if default == true
|
13
|
+
#special case where if name begins by no- we return false
|
14
|
+
if name =~ /^no-(.*)$/
|
15
|
+
return $1, false
|
16
|
+
else
|
17
|
+
return name, true
|
18
|
+
end
|
19
|
+
else
|
20
|
+
return name, default
|
21
|
+
end
|
22
|
+
else
|
23
|
+
value=val.join(sep)
|
24
|
+
return name,value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#takes a string as "name:value!option1=ploum!option2=plam,name2:value2!!globalopt=plim,globalopt2=plam"
|
29
|
+
#and return the hash
|
30
|
+
#{values: {name: value, name2: value2},
|
31
|
+
# localopt: {name: {option1:ploum,option2:plam}},
|
32
|
+
# globalopt: {globalopt: plim, globalopt2: plam},
|
33
|
+
# opt: {name: {option1:ploum,option2:plam,globalopt: plim, globalopt2: plam}, name2:{{globalopt: plim, globalopt2: plam}}}
|
34
|
+
def parse_string(s)
|
35
|
+
r={values: {}, local_opts: {}, global_opts: {}, opts: {}}
|
36
|
+
args,*globopts=s.split('!!')
|
37
|
+
globopts.map {|g| g.split(',')}.flatten.each do |g|
|
38
|
+
name,value=parse_namevalue(g, sep: '=', default: true)
|
39
|
+
r[:global_opts][name]=value
|
40
|
+
end
|
41
|
+
args.split(',').each do |arg|
|
42
|
+
arg,*localopts=arg.split('!')
|
43
|
+
name,value=parse_namevalue(arg)
|
44
|
+
r[:values][name]=value
|
45
|
+
r[:local_opts][name]={}
|
46
|
+
localopts.each do |o|
|
47
|
+
oname,ovalue=parse_namevalue(o, sep: '=', default: true)
|
48
|
+
r[:local_opts][name][oname]=ovalue
|
49
|
+
end
|
50
|
+
r[:local_opts].each do |name,hash|
|
51
|
+
r[:opts][name]=r[:local_opts][name].dup
|
52
|
+
r[:global_opts].each do |k,v|
|
53
|
+
r[:opts][name][k]||=v
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
return r
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'chronic'
|
2
|
+
require 'chronic_duration'
|
3
|
+
require 'active_support/time'
|
4
|
+
|
5
|
+
module DR
|
6
|
+
module TimeParse
|
7
|
+
extend self
|
8
|
+
def time_to_day_range(t)
|
9
|
+
return Chronic.parse(t.to_date, guess:false)
|
10
|
+
end
|
11
|
+
def parse(s, opt={})
|
12
|
+
return s if Date===s or Time===s
|
13
|
+
|
14
|
+
if !opt[:norange] && s=~/(.*)\.\.(.*)/
|
15
|
+
first=$1
|
16
|
+
second=$2
|
17
|
+
opt[:norange]=true
|
18
|
+
return Chronic::Span.new(self.parse(first, opt),self.parse(second,opt))
|
19
|
+
end
|
20
|
+
|
21
|
+
if not s.present?
|
22
|
+
t=Time.now
|
23
|
+
elsif s[0] =~ /[+-]/
|
24
|
+
#if s=+3.years-1.minutes
|
25
|
+
begin
|
26
|
+
t=eval(s)
|
27
|
+
rescue SyntaxError
|
28
|
+
#if s=3 years
|
29
|
+
t=ChronicDuration.parse(s[1...s.length])
|
30
|
+
t=-t if s[0]=='-'
|
31
|
+
end
|
32
|
+
case t
|
33
|
+
when Time
|
34
|
+
else
|
35
|
+
t=Time.now+t
|
36
|
+
end
|
37
|
+
return t
|
38
|
+
else
|
39
|
+
|
40
|
+
chronicopts=[:context,:now,:guess,:ambiguous_time_range,:endian_precedence,:ambiguous_year_future_bias]
|
41
|
+
chronicopt={hours24: true, ambiguous_time_range: 0, endian_precedence: [:little,:middle]}
|
42
|
+
chronicopt[:guess]=false if opt[:range]
|
43
|
+
chronicopt.update(opt.reject {|k,v| not chronicopts.include?(k)})
|
44
|
+
#puts chronicopt
|
45
|
+
t=Chronic.parse(s, chronicopt)
|
46
|
+
t=Time.parse(s) if not t
|
47
|
+
end
|
48
|
+
if opt[:range] && !opt[:norange] then
|
49
|
+
return time_to_day_range(t) unless Range === t
|
50
|
+
end
|
51
|
+
return t
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
#Examples:
|
57
|
+
#DR::TimeParse.parse("+100..tomorrow")
|
58
|
+
#first: +100, second: tomorrow
|
59
|
+
#=> 2014-08-22 11:20:31 +0200..2014-08-23 12:00:00 +0200
|
60
|
+
#[74] pry(main)> DR::TimeParse.parse("now..in seven days")
|
61
|
+
#first: now, second: in seven days
|
62
|
+
#=> 2014-08-22 11:20:25 +0200..2014-08-29 11:20:25 +0200
|
63
|
+
#[75] pry(main)> DR::TimeParse.parse("today")
|
64
|
+
#=> 2014-08-22 17:30:00 +0200
|
65
|
+
#[76] pry(main)> DR::TimeParse.parse("today",range: true)
|
66
|
+
#=> 2014-08-22 11:00:00 +0200..2014-08-23 00:00:00 +0200
|
67
|
+
#[181] pry(main)> DR::TimeParse.parse("-3 years 2 minutes")
|
68
|
+
#-94672920
|
69
|
+
#=> 2011-08-22 20:01:34 +0200
|
70
|
+
#[182] pry(main)> DR::TimeParse.parse("+3.years+2.days")
|
71
|
+
#=> 2017-08-24 14:04:08 +0200
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module DR
|
2
|
+
module CoreExt
|
3
|
+
#[Hash, Array].each {|m| m.include(Enumerable)} #to reinclude
|
4
|
+
module Enumerable
|
5
|
+
#Ex: [1,2,3,4].filter({odd: [1,3], default: :even})
|
6
|
+
#=> {:odd=>[1, 3], :even=>[2, 4]}
|
7
|
+
def filter(h)
|
8
|
+
invh=h.inverse
|
9
|
+
default=h[:default]
|
10
|
+
r={}
|
11
|
+
each do |el|
|
12
|
+
keys=invh.fetch(value,[default])
|
13
|
+
keys.each do |key|
|
14
|
+
(r[key]||=[]) << el
|
15
|
+
end
|
16
|
+
end
|
17
|
+
return r
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Hash
|
22
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
23
|
+
#
|
24
|
+
# h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
|
25
|
+
# h2 = { x: { y: [7,8,9] }, z: 'xyz' }
|
26
|
+
#
|
27
|
+
# h1.deep_merge(h2) #=> {x: {y: [7, 8, 9]}, z: "xyz"}
|
28
|
+
# h2.deep_merge(h1) #=> {x: {y: [4, 5, 6]}, z: [7, 8, 9]}
|
29
|
+
# h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) }
|
30
|
+
# #=> {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]}
|
31
|
+
#
|
32
|
+
# Adapted from active support
|
33
|
+
def deep_merge(other_hash, &block)
|
34
|
+
dup.deep_merge!(other_hash, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Same as +deep_merge+, but modifies +self+.
|
38
|
+
def deep_merge!(other_hash, &block)
|
39
|
+
return unless other_hash
|
40
|
+
other_hash.each_pair do |k,v|
|
41
|
+
tv = self[k]
|
42
|
+
case
|
43
|
+
when tv.is_a?(Hash) && v.is_a?(Hash)
|
44
|
+
self[k] = tv.deep_merge(v, &block)
|
45
|
+
when tv.is_a?(Array) && v.is_a?(Array)
|
46
|
+
if v.length > 0 && v.first.nil? then
|
47
|
+
#hack: if the array begins with nil, we append the new
|
48
|
+
#value rather than overwrite it
|
49
|
+
v.shift
|
50
|
+
self[k] += v
|
51
|
+
else
|
52
|
+
self[k] = block && tv ? block.call(k, tv, v) : v
|
53
|
+
end
|
54
|
+
when tv.nil? && v.is_a?(Array)
|
55
|
+
#here we still need to remove nil (see above)
|
56
|
+
if v.length > 0 && v.first.nil? then
|
57
|
+
v.shift
|
58
|
+
self[k]=v
|
59
|
+
else
|
60
|
+
self[k] = block && tv ? block.call(k, tv, v) : v
|
61
|
+
end
|
62
|
+
else
|
63
|
+
self[k] = block && tv ? block.call(k, tv, v) : v
|
64
|
+
end
|
65
|
+
end
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
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
|
72
|
+
def inverse
|
73
|
+
r={}
|
74
|
+
each_key do |k|
|
75
|
+
values=fetch(k)
|
76
|
+
values=[values] unless values.respond_to?(:each)
|
77
|
+
values.each do |v|
|
78
|
+
r[v]||=[]
|
79
|
+
r[v]<< k
|
80
|
+
end
|
81
|
+
end
|
82
|
+
return r
|
83
|
+
end
|
84
|
+
|
85
|
+
#sort the keys and the values of the hash
|
86
|
+
def sort_all
|
87
|
+
r=::Hash[self.sort]
|
88
|
+
r.each do |k,v|
|
89
|
+
r[k]=v.sort
|
90
|
+
end
|
91
|
+
return r
|
92
|
+
end
|
93
|
+
|
94
|
+
#take a key of the form ploum/plam/plim
|
95
|
+
#and return self[:ploum][:plam][:plim]
|
96
|
+
def keyed_value(key, sep: "/")
|
97
|
+
r=self.dup
|
98
|
+
return r if key.empty?
|
99
|
+
key.split(sep).each do |k|
|
100
|
+
k=k.to_sym if r.key?(k.to_sym) && !r.key?(k)
|
101
|
+
r=r[k]
|
102
|
+
end
|
103
|
+
return r
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
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)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
module UnboundMethod
|
125
|
+
#this should be in the stdlib...
|
126
|
+
def to_proc
|
127
|
+
return lambda do |obj,*args,&b|
|
128
|
+
self.bind(obj).call(*args,&b)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
def call(*args,&b)
|
132
|
+
self.to_proc.call(*args,&b)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
module Proc
|
137
|
+
#similar to curry, but pass the provided arguments on the right
|
138
|
+
#(a difference to Proc#curry is that we pass the argument directly, not
|
139
|
+
#via .call)
|
140
|
+
def rcurry(*args,&b)
|
141
|
+
return Proc.new do |*a,&b|
|
142
|
+
self.call(*a,*args,&b)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
#return self o g
|
147
|
+
#f.compose(g).(5,6)
|
148
|
+
def compose(g)
|
149
|
+
lambda do |*a,&b|
|
150
|
+
self.call(g.call(*a,&b))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
#(->(x) {->(y) {x+y}}).uncurry.(2,3) #=> 5
|
155
|
+
#(->(x,y) {x+y}).curry.uncurry.(2,3) #=>5
|
156
|
+
def uncurry
|
157
|
+
lambda do |*a|
|
158
|
+
a.reduce(self) {|fun,v| fun.call(v)}
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
module Array
|
164
|
+
#allows to do things like
|
165
|
+
# ["ploum","plam"].map(&[:+,"foo"]) #=> ["ploumfoo", "plamfoo"]
|
166
|
+
def to_proc
|
167
|
+
ar=self.dup
|
168
|
+
method=ar.shift.to_proc
|
169
|
+
return method.rcurry(*ar)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
module Object
|
174
|
+
#in ruby 2.2, 'Object#itself' only returns self
|
175
|
+
def this
|
176
|
+
return yield(self) if block_given?
|
177
|
+
return self
|
178
|
+
end
|
179
|
+
#simulate the Maybe monad
|
180
|
+
def and_this(&b)
|
181
|
+
nil? ? nil : this(&b)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
module Recursive
|
187
|
+
extend self
|
188
|
+
def recursive_constructor(klass)
|
189
|
+
return Class.new(klass) do |rklass|
|
190
|
+
define_method :initialize do |*args,&b|
|
191
|
+
b ? super(*args,&b) : super(*args) { |h,k| h[k] = rklass.new }
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
RecursiveHash=Recursive.recursive_constructor(Hash)
|
197
|
+
#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
|
+
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
|
@@ -0,0 +1,211 @@
|
|
1
|
+
module DR
|
2
|
+
module Meta
|
3
|
+
extend self
|
4
|
+
#from http://stackoverflow.com/questions/18551058/better-way-to-turn-a-ruby-class-into-a-module-than-using-refinements
|
5
|
+
#convert a class into a module using refinements
|
6
|
+
#ex: (Class.new { include Meta.refined_module(String) { def length; super+5; end } }).new("foo").length #=> 8
|
7
|
+
def refined_module(klass)
|
8
|
+
klass=klass.singleton_class unless Module===klass
|
9
|
+
Module.new do
|
10
|
+
include refine(klass) {
|
11
|
+
yield if block_given?
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def all_ancestors(obj)
|
17
|
+
obj=obj.singleton_class unless Module===obj
|
18
|
+
found=[]
|
19
|
+
stack=[obj]
|
20
|
+
while !stack.empty? do
|
21
|
+
obj=stack.shift
|
22
|
+
next if found.include?(obj)
|
23
|
+
found<<obj
|
24
|
+
stack.push(* obj.ancestors.select {|m| !(stack+found).include?(m)})
|
25
|
+
sing=obj.singleton_class
|
26
|
+
stack << sing unless sing.ancestors.select {|m| m.class==Module}.reduce(true) {|b,m| b && found.include?(m)}
|
27
|
+
end
|
28
|
+
return found
|
29
|
+
end
|
30
|
+
|
31
|
+
#add extend_ancestors and extend_complete to Object
|
32
|
+
def extend_object
|
33
|
+
include_ancestors=Meta.method(:include_ancestors)
|
34
|
+
include_complete=Meta.method(:include_complete)
|
35
|
+
Object.define_method(:extend_ancestors) do |m|
|
36
|
+
include_ancestors.bind(singleton_class).call(m)
|
37
|
+
end
|
38
|
+
Object.define_method(:extend_complete) do |m|
|
39
|
+
include_complete.bind(singleton_class).call(m)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#If we don't want to extend a module with Meta, we can still do
|
44
|
+
#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'
|
50
|
+
def apply(*args,method: nil, to: self, **opts,&block)
|
51
|
+
#note, in to self is Meta, except if we include it in another
|
52
|
+
#module so that it would make sense
|
53
|
+
method=method.unbind if method.class==Method
|
54
|
+
case method
|
55
|
+
when UnboundMethod
|
56
|
+
method=method.bind(to)
|
57
|
+
end
|
58
|
+
if opts.empty?
|
59
|
+
method.call(*args,&block)
|
60
|
+
else
|
61
|
+
method.call(*args,**opts,&block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_bound_method(obj, method_name, &block)
|
66
|
+
obj.singleton_class.send(:define_method,method_name, &block)
|
67
|
+
method = obj.method method_name
|
68
|
+
obj.singleton_class.send(:remove_method,method_name)
|
69
|
+
method
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#helping with metaprograming facilities
|
74
|
+
#usage: Module Foo; extend DR::MetaModule; include_complete Ploum; end
|
75
|
+
module MetaModule
|
76
|
+
#When included/extended (according to :hooks, add the following instance
|
77
|
+
#and class methods)
|
78
|
+
def includes_extends_host_with(instance_module=nil, class_module=nil, hooks: [:included,:extended])
|
79
|
+
@_include_module ||= []
|
80
|
+
@_extension_module ||= []
|
81
|
+
@_include_module << instance_module
|
82
|
+
@_extension_module << class_module
|
83
|
+
hooks.each do |hook|
|
84
|
+
define_singleton_method hook do |base|
|
85
|
+
#we use send here because :include is private in Module
|
86
|
+
@_include_module.each do |m|
|
87
|
+
m=const_get(m) if ! Module===m
|
88
|
+
base.send(:include, m)
|
89
|
+
end
|
90
|
+
@_extension_module.each do |m|
|
91
|
+
m=const_get(m) if ! Module===m
|
92
|
+
base.extend(m)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#include_ancestor includes all modules ancestor, so one can do
|
99
|
+
#singleton_class.include_ancestors(m) to have a fully featured extend
|
100
|
+
def include_ancestors(m)
|
101
|
+
ancestors=m.respond_to?(:ancestors) ? m.ancestors : m.singleton_class.ancestors
|
102
|
+
ancestors.reverse.each do |m|
|
103
|
+
include m if m.class==Module
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
#include a module and extend its singleton_class (along with its ancestors)
|
108
|
+
def include_complete(obj)
|
109
|
+
ancestors=Meta.all_ancestors(obj)
|
110
|
+
ancestors.reverse.each do |m|
|
111
|
+
include m if m.class==Module
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# module Z
|
116
|
+
# def x; "x"; end
|
117
|
+
# end
|
118
|
+
# module Enumerable
|
119
|
+
# extend MetaModule
|
120
|
+
# full_include Z
|
121
|
+
# end
|
122
|
+
# Array.new.x => "x"
|
123
|
+
def full_include other
|
124
|
+
include other
|
125
|
+
if self.class == Module
|
126
|
+
this = self
|
127
|
+
ObjectSpace.each_object Module do |mod|
|
128
|
+
mod.send :include, this if mod < self
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
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
|
+
#essentially like define_method, but can pass a Method or an UnboundMethod
|
147
|
+
#see also dr/core_ext which add UnboundMethod#to_proc so we could
|
148
|
+
#instead use define_method(name,&method) and it would work
|
149
|
+
def add_method(name=nil,method)
|
150
|
+
name=method.name unless name
|
151
|
+
name=name.to_sym
|
152
|
+
#if we have a (bound) method, we can convert it to a proc, but the
|
153
|
+
#'self' inside it keeps being the 'self' of the original object (even
|
154
|
+
#in instance_eval).
|
155
|
+
#Since usually we'll want to change the self, it's better to unbind it
|
156
|
+
method=method.unbind if method.class==Method
|
157
|
+
case method
|
158
|
+
when UnboundMethod
|
159
|
+
#here the block passed is evaluated using instance_eval, so self is the
|
160
|
+
#object calling, not the current module
|
161
|
+
define_method name do |*args,&block|
|
162
|
+
method.bind(self).call(*args,&block)
|
163
|
+
end
|
164
|
+
else
|
165
|
+
#if method is a block/Proc, this is the same as define_method(name,method)
|
166
|
+
define_method(name,&method)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def add_methods(*args)
|
171
|
+
return if args.empty?
|
172
|
+
if Module === args.first
|
173
|
+
mod=args.shift
|
174
|
+
#we include methods from mod, the arguments should be method names
|
175
|
+
if args.size == 1 and Hash === args.first
|
176
|
+
#we have a hash {new_name => old_name}
|
177
|
+
args.first.each do |k,v|
|
178
|
+
add_method(k,mod.instance_method(v.to_sym))
|
179
|
+
end
|
180
|
+
else
|
181
|
+
args.each do |m|
|
182
|
+
add_method(mod.instance_method(m.to_sym))
|
183
|
+
end
|
184
|
+
end
|
185
|
+
else
|
186
|
+
if args.size == 1 and Hash === args.first
|
187
|
+
#we have a hash {new_name => method}
|
188
|
+
args.first.each do |k,v|
|
189
|
+
add_method(k,v)
|
190
|
+
end
|
191
|
+
else
|
192
|
+
args.each do |m|
|
193
|
+
add_method(m)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
#DynamicModule.new(:methods_to_include) do ... end
|
201
|
+
class DynamicModule < Module
|
202
|
+
include MetaModule
|
203
|
+
|
204
|
+
def initialize(*args,&block)
|
205
|
+
super #call the block
|
206
|
+
add_methods(*args)
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|