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