rmtools 1.3.3 → 2.0.0.rc5

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.
@@ -34,6 +34,8 @@ module RMTools
34
34
  file.print = !format.q
35
35
  file.out = format.out || format.log_file
36
36
  file.color_out = format.color_out || format.color_log
37
+ file.detect_comments = !!format.detect_comments
38
+ file.precede_comments = format.precede_comments || "# "
37
39
 
38
40
  file.path_format = '%'.in file.out if file.out
39
41
  file.tf = format.time.to_a
@@ -44,21 +46,30 @@ module RMTools
44
46
  end
45
47
 
46
48
  def defaults
47
- puts %{ #common options:
49
+ puts %{ # #{@c.y 'common options:'}
48
50
  :q => false, # not print
49
51
  :out => false, # output to file, may contain strftime's %H%M%Y etc for filename
50
52
  :time => ["%H:%M:%S", "%03d"], # strftime, [msecs]
51
53
  :type => :console, # or :html
52
- #console values:
54
+ # #{@c.y 'console values:'}
53
55
  :caller => "#{@c.gray('%f:%l')} #{@c.red_bold(':%m')}", # "file:line :method", %p is for fullpath
54
56
  :format => "%time %mode [%caller]: %text" # format of entire log string, %mode is {#{%w(debug log info warn).map {|i| @highlight[i.to_sym]}*', '}}
55
- :color_out => false # do not clean control characters that make output to file colorful; set to true makes more readable output of `tail' but hardly readable by gui file output
56
- #html options:
57
+ :color_out => false, # do not clean control characters that make output to file colorful; set to true makes more readable output of `tail' but hardly readable by gui file output
58
+ :detect_comments => false, # highlight and strip comments blocks
59
+ :precede_comments => "# ",
60
+ # :detect_comments with default :precede_comments allows comment blocks looking like:
61
+ $log<<<<-'#'
62
+ # ... comment string one ...
63
+ # ... comment string two ...
64
+ #
65
+ be logged like #{@c.green "\n# ... comment string one ...\n# ... comment string two ..."}
66
+ # #{@c.y 'html options:'}
57
67
  :caller => "<a class='l'>%f:%l</a> <a class='m'>:%m</a>",
58
68
  :format => "<div class='line'><a class='t'>%time</a> <a class='%mode'>%mode</m> [%caller]: <p>%text</p>%att</div>", # %att is for array of objects that should be formatted by the next option
59
69
  :att =>"<div class='att'><div class='hide'>+</div><pre>%s</pre></div>", # .hide should be scripted to work like a spoiler
60
70
  :serializer => RMTools::RMLogger::HTML # should respond to :render(obj); nil value means each object will be just #inspect'ed}
61
71
  end
72
+ alias :usage :defaults
62
73
 
63
74
  # set any needed params, the rest will be set by default
64
75
  def set_format *args
@@ -95,6 +106,8 @@ module RMTools
95
106
  text = bind.report text
96
107
  elsif !text.is String
97
108
  text = text.inspect
109
+ elsif cfg.detect_comments and text =~ /\A[ \t]*#[ \t]+\S/
110
+ text = "\n" + @c.green(text.gsub(/^([ \t]*#[ \t])?/, cfg.precede_comments).chop)
98
111
  end
99
112
  out = cfg.out
100
113
  if cfg._time or cfg.path_format
@@ -7,7 +7,12 @@ module RMTools
7
7
  quiet, mute_warn = $quiet, $log.mute_warn
8
8
  $quiet = $log.mute_warn = true
9
9
  t1 = Time.now
10
- timez.times {yield} if timez > 0
10
+ begin
11
+ timez.times {yield} if timez > 0
12
+ rescue
13
+ $quiet, $log.mute_warn = quiet, mute_warn
14
+ raise $!
15
+ end
11
16
  res = yield
12
17
  t2 = Time.now
13
18
  ts.times {}
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
  RMTools::require 'dev/highlight'
3
3
  RMTools::require 'dev/logging'
4
+ require 'active_support/core_ext/class/attribute'
4
5
 
5
6
  module RMTools
6
7
 
@@ -71,16 +72,46 @@ module RMTools
71
72
  module_function :format_trace, :format_trace_to_html
72
73
  end
73
74
 
74
- class Class
75
+ # As for rmtools-1.1.0, 1.9.1 may hung up processing IO while generating traceback
76
+ # As for 1.2.10 with 1.9.3 with readline support it isn't hung up anymore
77
+
78
+ # Usage with Rails.
79
+ # Rails raise and rescue a bunch of exceptions during a first load, a reload of code (e.g. in development env) and maybe even some exceptions for each request.
80
+ # Thus, trace_format should be set only in a console environment *after* a code is loaded.
81
+ # For a web-server environment use RMTools.format_trace for inspect backtrace *after* an exception was rescued.
82
+ # Also note that Rails' autoreload of code won't rewrite SCRIPT_LINES__.
83
+ class Exception
84
+ alias :set_bt :set_backtrace
85
+ class_attribute :trace_format
75
86
 
76
- private
77
- def trace_format method
78
- if Exception.in ancestors
79
- class_attribute :__trace_format
80
- self.__trace_format = method
81
- else
82
- raise NoMethodError, "undefined method `trace_format' for class #{self}"
87
+ # If you also set (e.g. in irbrc file)
88
+ # module Readline
89
+ # alias :orig_readline :readline
90
+ # def readline(*args)
91
+ # ln = orig_readline(*args)
92
+ # SCRIPT_LINES__['(irb)'] << "#{ln}\n"
93
+ # ln
94
+ # end
95
+ # end
96
+ # it will be possible to fetch lines entered in IRB
97
+ # else format_trace would only read ordinally require'd files
98
+ def set_backtrace src
99
+ if format = self.class.trace_format
100
+ src = RMTools.__send__ format, src
83
101
  end
102
+ set_bt src
103
+ end
104
+ end
105
+
106
+ # This is the most usable setting, I think. Set it in the irbrc, config/initializers or wherever
107
+ <<-'example'
108
+ if defined? IRB
109
+ class StandardError
110
+ self.trace_format = :format_trace
84
111
  end
85
112
 
86
- end
113
+ class SystemStackError
114
+ self.trace_format = nil
115
+ end
116
+ end
117
+ example
@@ -1,10 +1,10 @@
1
1
  # encoding: utf-8
2
- class BlackHole
2
+ class Void
3
3
  __init__
4
4
 
5
- # abc = BlackHole.new
5
+ # abc = Void.new
6
6
  # (abc.first.get {|_| !_}.something << 'blah blah')[123].raise!
7
- # => #<BlackHole:0xb66367b0>
7
+ # => #<Void:0xb66367b0>
8
8
  #
9
9
  # Think twice before use it. It may devour your code!
10
10
  def method_missing(m, *args)
@@ -21,4 +21,6 @@ class BlackHole
21
21
  if RUBY_VERSION < '1.9'
22
22
  undef id
23
23
  end
24
- end
24
+ end
25
+
26
+ BlackHole = Void
@@ -61,15 +61,20 @@ class Array
61
61
  end
62
62
 
63
63
  alias diff ^
64
+
65
+ def intersects?(ary)
66
+ (self & ary).any?
67
+ end
68
+ alias :x? :intersects?
64
69
 
65
70
  # arithmetics
66
71
  def avg
67
- sum.to_f/size
72
+ empty? ? 0 : sum.to_f/size
68
73
  end
69
74
 
70
75
  # for use with iterators
71
76
  def avg_by(&b)
72
- sum(&b).to_f/size
77
+ empty? ? 0 : sum(&b).to_f/size
73
78
  end
74
79
 
75
80
  def scale(top)
@@ -252,4 +257,9 @@ class Array
252
257
  nil
253
258
  end
254
259
 
260
+ # rightmost #uniq
261
+ def runiq
262
+ reverse.uniq.reverse
263
+ end
264
+
255
265
  end
@@ -1,57 +1,300 @@
1
1
  # encoding: utf-8
2
2
  RMTools::require 'enumerable/array'
3
3
 
4
- unless defined? RMTools::Iterators
4
+ # [1, 2, 3].to_ss # => ['1', '2', '3']
5
+ # [[1,2,3], [4,5,6], [7,8,9]].to_sss
6
+ # => [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]]
7
+ # [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]].subss!(/\d+/) {|m| (m.to_i % 3).to_s}
8
+ # => [["1", "2", "0"], ["1", "2", "0"], ["1", "2", "0"]]
9
+ # [[1, 2, 0], [1, 2, 0], [1, 2, 0]].sum_zeros?
10
+ # => [false, false, true, false, false, true, false, false, true]
11
+ # [[1, 2, 3], [3, 4, 6], [3, 8, 0]].uniq_by_odds?
12
+ # => [[1, 2, 3], [3, 4, 6]]
13
+ class Array
14
+ alias :throw_no :method_missing
15
+ mattr_reader :iterators_names, :iterators_pattern
16
+ @@iterators_names = []
5
17
 
6
- # [1, 2, 3].to_ss # => ['1', '2', '3']
7
- # [[1,2,3], [4,5,6], [7,8,9]].to_sss
8
- # => [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]]
9
- # [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]].subss!(/\d+/) {|m| (m.to_i % 3).to_s}
10
- # => [["1", "2", "0"], ["1", "2", "0"], ["1", "2", "0"]]
11
- # [[1, 2, 0], [1, 2, 0], [1, 2, 0]].sum_zeros?
12
- # => [false, false, true, false, false, true, false, false, true]
13
- # [[1, 2, 3], [3, 4, 6], [3, 8, 0]].uniq_by_odds?
14
- # => [[1, 2, 3], [3, 4, 6]]
15
- class Array
16
- alias :throw_no :method_missing
17
- RMTools::Iterators = %r{^(#{(instance_methods.grep(/_by$/)+%w{every no select reject partition find_all find sum foldr})*'|'})_([\w\d\_]+[!?]?)}
18
+ class << self
18
19
 
19
- def method_missing(method, *args, &block)
20
- if match = (meth = method.to_s).match(RMTools::Iterators)
21
- iterator, meth = match[1].to_sym, match[2].to_sym
22
- begin
23
- case iterator
24
- when :every then iterator = :every?
25
- when :no then iterator = :no?
20
+ def add_iterator_name(name_or_list)
21
+ name_or_list = [name_or_list] if !name_or_list.is Array
22
+ @@iterators_names |= name_or_list
23
+ @@iterators_pattern = %r{^(#{@@iterators_names*'|'})_([\w\d\_]+[!?]?)}
24
+ end
25
+
26
+ def fallback_to_clean_iterators!
27
+ class_eval do
28
+ # Benchmark 1:
29
+ # # We take a simple methods like uniq_by (O(N)) and odd? (O(1)) to ensure that
30
+ # # penalty we would have in production would not be larger than that in bench
31
+ #
32
+ # # 1.1. Traditional calls:
33
+ # # 1.1.1: (9 x #odd? + 3 x #map + 1 x #uniq_by) * 10^6
34
+ # timer(1_000_000) { [[1, 2, 3], [3, 4, 6], [3, 8, 0]].uniq_by {|i| i.map {|j| j.odd?}} }
35
+ # one: 0.0130ms, total: 13040.0ms
36
+ # # 1.1.2: (90_000 x #odd? + 300 x #map + 1 x #uniq_by) * 100
37
+ # timer(100) { a.uniq_by {|i| i.map {|j| j.odd?}} }
38
+ # one: 34.0000ms, total: 3400.0ms
39
+ #
40
+ # # 1.2. Meta calls:
41
+ # # 1.2.1: += (13 * 10^6 x #__send__) + (4 * 10^6 x #method_missing)
42
+ # timer(1_000_000) { [[1, 2, 3], [3, 4, 6], [3, 8, 0]].uniq_by_odds? }
43
+ # one: 0.0354ms, total: 35440.0ms
44
+ # # += 172% of time
45
+ # a = (0...300).to_a.map {Array.rand 300};
46
+ # # 1.2.2: += (9 * 10^6 x #__send__) + (30_100 x #method_missing)
47
+ # timer(100) { a.uniq_by_odds? }
48
+ # one: 39.3000ms, total: 3930.0ms
49
+ # # += 16% of time
50
+ #
51
+ # Conclusion:
52
+ #
53
+ # 1. If we want to speed meta-calls up, we should sacrifice cleanness of Array namespace,
54
+ # I mean define missing methods inplace.
55
+ # 2. Most latency is given by #method_missing, but which are factor of #__send__?
56
+ def method_missing(method, *args, &block)
57
+ if match = (meth = method.to_s).match(@@iterators_pattern)
58
+ iterator, meth = match[1].to_sym, match[2].to_sym
59
+
60
+ begin
61
+ case iterator
62
+ when :every then iterator = :every?
63
+ when :no then iterator = :no?
64
+ end
65
+
66
+ return case iterator
67
+ when :sum, :sort_along_by; __send__(iterator, args.shift) {|i| i.__send__ meth, *args, &block}
68
+ when :find_by, :select_by, :reject_by; __send__(iterator, meth, *args)
69
+ else __send__(iterator) {|i| i.__send__ meth, *args, &block}
70
+ end
71
+ rescue NoMethodError => e
72
+ e.message << " (`#{method}' interpreted as decorator-function `#{meth}')"
73
+ raise e
74
+ end
75
+
76
+ elsif meth.sub!(/sses([=!?]?)$/, 'ss\1') or meth.sub!(/ies([=!?]?)$/, 'y\1') or meth.sub!(/s([=!?]?)$/, '\1')
77
+ assignment = meth =~ /=$/
78
+ meth = meth.to_sym
79
+
80
+ begin
81
+ if assignment
82
+ if Array === args
83
+ each_with_index {|e,i| e.__send__ meth, args[i]}
84
+ else
85
+ each {|e| e.__send__ meth, args}
86
+ end
87
+ else map {|e| e.__send__ meth, *args, &block}
88
+ end
89
+ rescue NoMethodError => e
90
+ e.message << " (`#{method}' interpreted as map-function `#{meth}')"
91
+ raise e
92
+ end
93
+
94
+ else
95
+ throw_no method
96
+ end
97
+ end
98
+ end # class_eval
99
+ end # def fallback_to_clean_iterators!
100
+
101
+ end # << self
102
+
103
+ add_iterator_name(instance_methods.grep(/_by$/)+%w{every no select reject partition find_all find sum foldr foldl fold count rand_by})
104
+
105
+ # Benchmark 2:
106
+ #
107
+ # # 2.2. Meta calls:
108
+ # # 2.2.1: += (13 * 10^6 x #__send__)
109
+ # timer(1_000_000) { [[1, 2, 3], [3, 4, 6], [3, 8, 0]].uniq_by_odds? }
110
+ # one: 0.0156ms, total: 15570.0ms
111
+ # # += 19% of time
112
+ # a = (0...300).to_a.map {Array.rand 300};
113
+ # # 2.2.2: += (9 * 10^6 x #__send__)
114
+ # timer(100) { a.uniq_by_odds? }
115
+ # one: 37.9000ms, total: 3790.0ms
116
+ # # += 11% of time
117
+ <<-'version 2'
118
+ def method_missing(method, *args, &block)
119
+ if match = (meth = method.to_s).match(@@iterators_pattern)
120
+ iterator, meth = match[1].to_sym, match[2].to_sym
121
+ case iterator
122
+ when :every then iterator = :every?
123
+ when :no then iterator = :no?
124
+ end
125
+
126
+ Array.class_eval do
127
+ case iterator
128
+ when :sum, :sort_along_by
129
+ define_method method do |*args, &block|
130
+ begin
131
+ # sum_posts_ids([], :all) =>
132
+ # sum([]) {|e| e.posts_ids(:all)}
133
+ __send__(iterator, args.shift) {|i| e.__send__ meth, *args, &block}
134
+ rescue NoMethodError => err
135
+ err.message << " (`#{method}' interpreted as decorator-function `#{meth}')"
136
+ raise err
137
+ end
138
+ end
139
+ when :find_by, :select_by, :reject_by
140
+ define_method method do |*args, &block|
141
+ begin
142
+ # select_by_count(max_count) =>
143
+ # select {|e| e.count == max_count}
144
+ __send__(iterator, meth, *args)
145
+ rescue NoMethodError => err
146
+ err.message << " (`#{method}' interpreted as decorator-function `#{meth}')"
147
+ raise err
148
+ end
26
149
  end
27
- return case iterator
28
- when :sum; sum(args.shift) {|i| i.__send__ meth, *args, &block}
29
- when :find_by, :select_by, :reject_by; __send__(iterator, meth, *args)
30
- else __send__(iterator) {|i| i.__send__ meth, *args, &block}
150
+ else
151
+ define_method method do |*args, &block|
152
+ begin
153
+ # uniq_by_sum(1) {|i| 1 / i.weight} =>
154
+ # uniq_by {|e| e.sum(1) {|i| 1 / i .weight}}
155
+ __send__(iterator) {|e| e.__send__ meth, *args, &block}
156
+ rescue NoMethodError => err
157
+ err.message << " (`#{method}' interpreted as decorator-function `#{meth}')"
158
+ raise err
31
159
  end
32
- rescue NoMethodError => e
33
- e.message << " (`#{method}' interpreted as decorator-function `#{meth}')"
34
- raise e
160
+ end
35
161
  end
36
- elsif meth.sub!(/sses([=!?]?)$/, 'ss\1') or meth.sub!(/ies([=!?]?)$/, 'y\1') or meth.sub!(/s([=!?]?)$/, '\1')
37
- assignment = meth =~ /=$/
38
- meth = meth.to_sym
39
- begin
40
- if assignment
41
- if Array === args
42
- each_with_index {|e,i| e.__send__ meth, args[i]}
43
- else
44
- each {|e| e.__send__ meth, args}
162
+ end
163
+
164
+ elsif meth.sub!(/sses([=!?]?)$/, 'ss\1') or meth.sub!(/ies([=!?]?)$/, 'y\1') or meth.sub!(/s([=!?]?)$/, '\1')
165
+ assignment = meth =~ /=$/
166
+ meth = meth.to_sym
167
+
168
+ Array.class_eval do
169
+ if assignment
170
+ define_method method do |value|
171
+ begin
172
+ if Array === value
173
+ # owner_ids = users_ids =>
174
+ # each_with_index {|e, i| e.owner_id = users_ids[i]}
175
+ each_with_index {|e, i| e.__send__ meth, value[i]}
176
+ else
177
+ # owner_ids = user_id =>
178
+ # each {|e, i| e.owner_id = user_id}
179
+ each {|e| e.__send__ meth, value}
180
+ end
181
+ rescue NoMethodError => e
182
+ e.message << " (`#{method}' interpreted as map-function `#{meth}')"
183
+ raise e
184
+ end
185
+ end
186
+ else
187
+ define_method method do |*args, &block|
188
+ begin
189
+ # to_is(16) =>
190
+ # map {|e| e.to_i(16)}
191
+ map {|e| e.__send__ meth, *args, &block}
192
+ rescue NoMethodError => err
193
+ err.message << " (`#{method}' interpreted as map-function `#{meth}')"
194
+ raise err
45
195
  end
46
- else map {|e| e.__send__ meth, *args, &block}
47
196
  end
48
- rescue NoMethodError => e
49
- e.message << " (`#{method}' interpreted as map-function `#{meth}')"
50
- raise e
51
197
  end
52
- else throw_no method
53
- end
54
- end
198
+ end
199
+
200
+ else
201
+ return throw_no method
202
+ end
203
+
204
+ __send__(method, *args, &block)
55
205
  end
56
-
206
+ version 2
207
+
208
+ # Benchmark 3:
209
+ #
210
+ # # 3.2. Meta calls:
211
+ # # 3.2.1: += (13 * 10^6 x #__send__)
212
+ # timer(1_000_000) { [[1, 2, 3], [3, 4, 6], [3, 8, 0]].uniq_by_odds? }
213
+ # one: 0.0145ms, total: 14520.0ms
214
+ # # += 11% of time
215
+ # a = (0...300).to_a.map {Array.rand 300};
216
+ # # 3.2.2: += (9 * 10^6 x #__send__)
217
+ # timer(100) { a.uniq_by_odds? }
218
+ # one: 36.1000ms, total: 3610.0ms
219
+ # # += 6% of time
220
+ def method_missing(method, *args, &block)
221
+ if match = (meth = method.to_s).match(@@iterators_pattern)
222
+ iterator, meth = match[1].to_sym, match[2].to_sym
223
+ case iterator
224
+ when :every then iterator = :every?
225
+ when :no then iterator = :no?
226
+ end
227
+
228
+ case iterator
229
+ when :sum, :sort_along_by
230
+ Array.class_eval %{
231
+ def #{method}(*args, &block)
232
+ # sum_posts_ids([], :all) =>
233
+ # sum([]) {|e| e.posts_ids(:all)}
234
+ #{iterator}(args.shift) {|e| e.#{meth}(*args, &block)}
235
+ rescue NoMethodError => err
236
+ err.message << " (`#{method}' interpreted as decorator-function `#{meth}')"
237
+ raise err
238
+ end}
239
+ when :find_by, :select_by, :reject_by
240
+ Array.class_eval %{
241
+ def #{method}(val)
242
+ # select_by_count(max_count) =>
243
+ # select {|e| e.count == max_count}
244
+ #{iterator.to_s[0...-3]} {|e| e.#{meth} == val}
245
+ rescue NoMethodError => err
246
+ err.message << " (`#{method}' interpreted as decorator-function `#{meth}')"
247
+ raise err
248
+ end}
249
+ else
250
+ Array.class_eval %{
251
+ def #{method}(*args, &block)
252
+ # uniq_by_sum(1) {|i| 1 / i.weight} =>
253
+ # uniq_by {|e| e.sum(1) {|i| 1 / i .weight}}
254
+ #{iterator} {|e| e.#{meth}(*args, &block)}
255
+ rescue NoMethodError => err
256
+ err.message << " (`#{method}' interpreted as decorator-function `#{meth}')"
257
+ raise err
258
+ end}
259
+ end
260
+
261
+ elsif meth.sub!(/sses([=!?]?)$/, 'ss\1') or meth.sub!(/ies([=!?]?)$/, 'y\1') or meth.sub!(/s([=!?]?)$/, '\1')
262
+ assignment = meth =~ /=$/
263
+ meth = meth.to_sym
264
+
265
+ if assignment
266
+ Array.class_eval %{
267
+ def #{method}(value)
268
+ if Array === value
269
+ # owner_ids = users_ids =>
270
+ # each_with_index {|e, i| e.owner_id = users_ids[i]}
271
+ each_with_index {|e, i| e.__send__ meth, value[i]}
272
+ else
273
+ # owner_ids = user_id =>
274
+ # each {|e, i| e.owner_id = user_id}
275
+ each {|e| e.__send__ meth, value}
276
+ end
277
+ rescue NoMethodError => err
278
+ err.message << " (`#{method}' interpreted as map-function `#{meth}')"
279
+ raise err
280
+ end}
281
+ else
282
+ Array.class_eval %{
283
+ def #{method}(*args, &block)
284
+ # to_is(16) =>
285
+ # map {|e| e.to_i(16)}
286
+ map {|e| e.#{meth}(*args, &block)}
287
+ rescue NoMethodError => err
288
+ err.message << " (`#{method}' interpreted as map-function `#{meth}')"
289
+ raise err
290
+ end}
291
+ end
292
+
293
+ else
294
+ return throw_no method
295
+ end
296
+
297
+ __send__(method, *args, &block)
298
+ end
299
+
57
300
  end
@@ -5,6 +5,7 @@ RMTools::require 'enumerable/traversal'
5
5
  class Hash
6
6
  include RMTools::KeyValueTraversal
7
7
  alias :>> :reverse_merge!
8
+ alias :<< :merge!
8
9
 
9
10
  def to_traversable
10
11
  RMTools::KeyValueTraversable.new(self)