drain 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ #for the filename ploum.rb, load all ploum/*.rb files
2
+ dir=File.expand_path(File.basename(__FILE__).chomp('.rb'), File.dirname(__FILE__))
3
+ Dir.glob(File.expand_path('*.rb',dir)) do |file|
4
+ require file
5
+ end
@@ -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,5 @@
1
+ #for the filename ploum.rb, load all ploum/*.rb files
2
+ dir=File.expand_path(File.basename(__FILE__).chomp('.rb'), File.dirname(__FILE__))
3
+ Dir.glob(File.expand_path('*.rb',dir)) do |file|
4
+ require file
5
+ end
@@ -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
+