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.
@@ -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
+