rmtools 1.2.0 → 1.2.2b

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt CHANGED
@@ -17,8 +17,8 @@ lib/rmtools/dev/binding.rb
17
17
  lib/rmtools/dev/logging.rb
18
18
  lib/rmtools/dev/trace_format.rb
19
19
  lib/rmtools/dev/highlight.rb
20
+ lib/rmtools/dev/code_reading.rb
20
21
  lib/rmtools/dev/timer.rb
21
- lib/rmtools/dev/code_reader.rb
22
22
  lib/rmtools/xml/xpath.rb
23
23
  lib/rmtools/xml/string.rb
24
24
  lib/rmtools/xml/libxml.rb
@@ -33,6 +33,7 @@ lib/rmtools/core/string_compliance.rb
33
33
  lib/rmtools/core/kernel.rb
34
34
  lib/rmtools/core/boolean.rb
35
35
  lib/rmtools/core/aliases.rb
36
+ lib/rmtools/core/threadify.rb
36
37
  lib/rmtools/core/module.rb
37
38
  lib/rmtools/core/object.rb
38
39
  lib/rmtools/core/deprecation.rb
@@ -66,7 +67,6 @@ lib/rmtools/fs.rb
66
67
  lib/rmtools/ip.rb
67
68
  lib/rmtools/lang.rb
68
69
  lib/rmtools/functional.rb
69
- lib/rmtools/load.rb
70
70
  lib/rmtools/install.rb
71
71
  lib/rmtools/functional/decorate.rb
72
72
  lib/rmtools/functional/fold.rb
@@ -88,6 +88,7 @@ lib/rmtools/conversions/string.rb
88
88
  lib/rmtools/conversions/enum.rb
89
89
  lib/rmtools/conversions/int.rb
90
90
  lib/rmtools/enumerable.rb
91
+ lib/rmtools/init.rb
91
92
  lib/rmtools/dev_min.rb
92
93
  lib/rmtools_dev.rb
93
94
  ./Rakefile
data/README.txt CHANGED
@@ -14,7 +14,7 @@ Methods for basic classes addon collection.
14
14
  * Slightly extended StringScanner
15
15
  * Proof of concept: Regexp reverse (wonder if someone did it earlier in Ruby)
16
16
  * Kernel#whose? to find classes and/or modules knowing some method
17
- * Method code lookup over all loaded libs (it can't handle evals yet), see dev/code_lookup.rb
17
+ * Method code lookup over all loaded libs (it can't handle evals yet), see dev/code_reading.rb
18
18
  * Coloring is now made by singleton `Painter' and have option for transparent coloring
19
19
 
20
20
  === Version 1.1.14
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'rake'
2
2
  require 'lib/rmtools/install'
3
3
  compile_manifest
4
4
 
5
- RMTOOLS_VERSION = '1.2.0'
5
+ RMTOOLS_VERSION = '1.2.2b'
6
6
  begin
7
7
  require 'hoe'
8
8
  config = Hoe.spec 'rmtools' do
data/ext/rmtools.cpp CHANGED
@@ -20,15 +20,17 @@ using namespace std;
20
20
  static VALUE object_define_new_value(VALUE self, VALUE new_obj)
21
21
  {
22
22
  if (FIXNUM_P(self) || self == Qnil || self == Qfalse || self == Qtrue || self == Qundef) {
23
- const char* msg = STR2CSTR(rb_mod_name(rb_obj_class(self)));
23
+ VALUE tmp = rb_mod_name(rb_obj_class(self));
24
+ const char* msg = StringValuePtr(tmp);
24
25
  rb_raise(rb_eTypeError, "can't redefine %s", msg);
25
26
  }
26
27
  if (FIXNUM_P(new_obj) || new_obj == Qnil || new_obj == Qfalse || new_obj == Qtrue || new_obj == Qundef) {
27
- const char* msg = STR2CSTR(rb_mod_name(rb_obj_class(new_obj)));
28
+ VALUE tmp = rb_mod_name(rb_obj_class(self));
29
+ const char* msg = StringValuePtr(tmp);
28
30
  rb_raise(rb_eTypeError, "can't define object as %s", msg);
29
31
  }
30
32
  // Place the definition of the new object in the slot of self
31
- memcpy(reinterpret_cast<void*>(self), reinterpret_cast<void*>(new_obj), SLOT_SIZE);
33
+ memcpy(reinterpret_cast<void*>(self), reinterpret_cast<void*>(rb_funcall(new_obj, rb_intern("clone"), 0)), SLOT_SIZE);
32
34
  return self;
33
35
  }
34
36
 
data/lib/rmtools.rb CHANGED
@@ -1,2 +1,2 @@
1
- require 'rmtools/load'
1
+ require 'rmtools/init'
2
2
  RMTools::require 'dev_min'
@@ -12,7 +12,7 @@ class Array
12
12
  if Hash === defaults
13
13
  opts, defaults = defaults, []
14
14
  return_hash = true
15
- $log.warn "fetch_opts(<hash>) now changed, if you want to jsut fetch hash options, use `opts = <hash>.merge(opts||{})' construction"
15
+ $log.warn "fetch_opts(<hash>) now changed, if you want to jsut fetch hash options, use `opts = <hash>.merge(opts||{})' construction", :caller => 2
16
16
  else
17
17
  return_hash = false
18
18
  end
@@ -46,4 +46,4 @@ class Class
46
46
 
47
47
  end
48
48
 
49
- [Hash, Regexp, File, Dir].each {|klass| klass.__init__}
49
+ [Hash, Regexp, File, Dir, Range, Class, Module].each {|klass| klass.__init__}
@@ -3,6 +3,8 @@ module Kernel
3
3
 
4
4
  # re-require
5
5
  def require!(file)
6
+ ['.rb', '.so', '.dll', ''].each {|ext| $".delete "#{file}#{ext}"}
7
+ file = File.expand_path file
6
8
  ['.rb', '.so', '.dll', ''].each {|ext| $".delete "#{file}#{ext}"}
7
9
  require file
8
10
  end
@@ -23,12 +25,13 @@ module Kernel
23
25
  end
24
26
 
25
27
  def executing? file=$0
26
- caller(0)[0] =~ /^#{file}:/
28
+ caller[0] =~ /^#{file}:/
27
29
  end
28
30
 
29
31
  def whose?(method, *opts)
30
- opts = opts.get_opts [:flags], :ns => :public
31
- checker = :"#{ns}_method_defined?"
32
+ opts = *opts.get_opts([:flags], :ns => :public)
33
+ opts[:modules] ||= opts[:mod]
34
+ checker = :"#{opts[:ns]}_method_defined?"
32
35
  if Array === method
33
36
  methods = method.to_syms
34
37
  else
@@ -46,6 +46,27 @@ class Object
46
46
 
47
47
  def in(*container)
48
48
  container.size == 1 ? container[0].include?(self) : container.include?(self)
49
+ end
50
+
51
+ def deep_clone
52
+ _deep_clone({})
53
+ end
54
+
55
+ protected
56
+ def _deep_clone(cloning_map)
57
+ return cloning_map[self] if cloning_map.key? self
58
+ cloning_obj = clone
59
+ cloning_map[self] = cloning_obj
60
+ cloning_obj.instance_variables.each do |var|
61
+ val = cloning_obj.instance_variable_get(var)
62
+ begin
63
+ val = val._deep_clone(cloning_map)
64
+ rescue TypeError
65
+ next
66
+ end
67
+ cloning_obj.instance_variable_set(var, val)
68
+ end
69
+ cloning_obj
49
70
  end
50
71
 
51
72
  end
@@ -0,0 +1,17 @@
1
+ module RMTools
2
+
3
+ def threadify(ary, max_threads=4)
4
+ forks = []
5
+ ary.each do |e|
6
+ if max_threads > forks.size
7
+ forks << fork { yield e }
8
+ end
9
+ if max_threads == forks.size
10
+ forks.delete Process.wait
11
+ end
12
+ end
13
+ Process.waitall
14
+ end
15
+
16
+ module_function:threadify
17
+ end
@@ -76,6 +76,33 @@ module ActiveRecord
76
76
  find_by_sql(["SELECT * FROM #{quoted_table_name} WHERE #{primary_key} = ?", id])[0]
77
77
  end
78
78
 
79
+ # values must be 2-tuple array [[column1, value1], [column2, value2], ...] and columns must be in order they've been created
80
+ def self.insert_unless_exist(table, values)
81
+ table = connection.quote_table_name table
82
+ if execute_sanitized(["SELECT COUNT(*) FROM #{table} WHERE #{vaues.firsts.map {|v| "#{connection.quote_column_name v}=?"}*' AND '} LIMIT 1", *values.lasts]).to_a.flatten > 0
83
+ false
84
+ else
85
+ execute_sanitized ["INSERT INTO #{table} VALUES (#{['?']*values.size*','})", *values.lasts]
86
+ true
87
+ end
88
+ end
89
+
90
+ def resource_path
91
+ "#{self.class.name.tableize}/#{id}"
92
+ end
93
+
94
+ end
95
+
96
+ class Relation
97
+
98
+ def any?
99
+ limit(1).count != 0
100
+ end
101
+
102
+ def empty?
103
+ limit(1).count == 0
104
+ end
105
+
79
106
  end
80
107
 
81
108
  end
@@ -0,0 +1,556 @@
1
+ # encoding: utf-8
2
+ RMTools::require 'text/string_scanner'
3
+
4
+ module RMTools
5
+ class CodeReader
6
+ attr_reader :MethodCache, :stack
7
+
8
+ module Defaults
9
+ Leftop = leftop = '\[{(<=>~+\-*,;:^&\|'
10
+ rightop = '\])}?!'
11
+ sname = '\w<=>~+\-*/\]\[\^%!?@&\|'
12
+ compname = "([\\.:#{sname}]+)"
13
+ name = "([#{sname}]+)"
14
+ mname = '([:\w]+)'
15
+ call_ = '(\{s*\||([\w][?!]?|\)|\.((\[\]|[<=>])?=[=~]?|[<>*\-+/%^&\|]+))[ \t]*\{)'
16
+ space = '[ \t]+'
17
+ space_ = '[ \t]*'
18
+ kw = 'if|elsif|else|unless|while|until|case|begin|rescue|when|then|or|and|not'
19
+ heredoc_handle = %q{<<(-)?(\w+|`[^`]+`|'[^']+'|"[^"]+")}
20
+ heredoc = %{([\\s#{leftop}?]|[#{leftop}\\w!?][ \t])\\s*#{heredoc_handle}}
21
+ re_sugar = %{(^|[#{leftop}\\n?;]|\W!|\\b(#{kw})[ \t]|[\\w#{rightop}]/)\\s*/}
22
+ percent = '%([xwqrQW])?([\\/<({\[!\|])'
23
+ simple = [re_sugar, percent, '[#\'"`]']*'|'
24
+ mod_def = "module +#{mname}"
25
+ class_def = "class(?: *(<<) *| +)([$@:\\w]+)(?: *< *#{mname})?"
26
+ method_def = "def +#{compname}"
27
+ alias_def = "alias +:?#{name} +:?#{name}"
28
+ Ender = '\s*\)? *(?:end\b|[\n;})\]])'
29
+
30
+ StringParseRE = /#{heredoc}|#{simple}|[{}]|[^\w#{rightop}'"`\/\\]\?\\?\S/m
31
+ HeredocParseRE = /\n|#{heredoc}|#{simple}/m
32
+ ArgumentsParseRE = /#{simple}|[{}()\[\]\n;,\|]| end\b|[:@$.\w!?]+(\b|[( ] *)| +[:?]\s+| *=>\s*/m
33
+
34
+ StringRE = /(^['"`]$)|^#{percent}$/
35
+ RERE = %r{(?:^|[#{leftop}\w!\s/])\s*(/)}
36
+ HeredocRE = heredoc_handle.to_re
37
+ Symbol = /^:#{name}$/
38
+ Attrs = /\s(c)?(?:attr_(reader|writer|accessor))[( ] *((?::\w+ *,\s*)*:\w+)#{Ender}/
39
+ Include = /\s(include|extend) +#{mname}/
40
+ AliasMethod = /\salias_method :#{name} *,\s*:#{name}/
41
+ Beginners = /(([#{leftop}\n]?\s*)(if|unless|while|until))#{
42
+ }|(.)?(?:(do|for)|begin|case)/
43
+ EOF = /($0\s*==\s*__FILE__\s*|__FILE__\s*==\s*\$0\s*)?\n/
44
+ BlockOpen = /(?:^\{\s*\||.\{)$/
45
+ Ord = /^\W\?\\?\S$/
46
+
47
+ MainParseRE = /#{simple
48
+ }|#{call_}|[{}]#{
49
+ }|(^|\n)=begin\b#{
50
+ }|^\s*[;\(]? *(#{mod_def}|#{method_def})#{
51
+ }|:#{name}#{
52
+ }|[^\w#{rightop}'"`\/\\]\?\\?\S#{
53
+ }|#{heredoc
54
+ }|(^|[#{leftop}\n])\s*((if|unless)\b|#{
55
+ }[;\(]? *#{class_def})#{
56
+ }|(^|[\n;])\s*(while|until)\b#{
57
+ }|(^|[#{leftop}\s?])(do|case|begin|for)\b#{
58
+ }\s(c)?(?:attr_(reader|writer|accessor))[( ] *((?::\w+ *,\s*)*:\w+)#{Ender
59
+ }|\salias_method +:#{name} *,\s*:#{name
60
+ }|\s(include|extend)[( ] *#{mname
61
+ }|(^|[;\s])(#{alias_def}|end|__END__)\b/m
62
+
63
+ ModDef = mod_def.to_re
64
+ ClassDef = class_def.to_re
65
+ MethodDef = method_def.to_re
66
+ AliasDef = alias_def.to_re
67
+
68
+ def self::Class(s, m)
69
+ debug
70
+ _stack = clean_stack
71
+ if _stack[-1] == [:block]
72
+ stack << [:beginner]
73
+ elsif m[1]
74
+ if m[2] =~ /^[@$]/
75
+ stack << [:beginner]
76
+ elsif _stack.any? and _stack[-1][0] == :def
77
+ stack << [:beginner]
78
+ else
79
+ slf = _stack.lasts*'::'
80
+ name = m[2].sub 'self.', ''
81
+ name.sub! 'self', slf
82
+ name = fix_module_name slf, name
83
+ stack << [:singleton, name]
84
+ end
85
+ else
86
+ new = clean_stack.lasts*'::'
87
+ stack << [:class, m[2]]
88
+ name = fix_module_name new, m[3] if m[3]
89
+ new << '::' if new.b
90
+ new << m[2]
91
+ MethodCache[new] ||= {}
92
+ inherit! new, name if m[3]
93
+ end
94
+ end
95
+
96
+ def self::Module(s, m)
97
+ debug
98
+ stack << [:mod, m[1]]
99
+ MethodCache[clean_stack.lasts*'::'] = {}
100
+ end
101
+
102
+ def self::Method(s, m)
103
+ debug
104
+ _stack = clean_stack(true)
105
+ if _stack[-1] == [:block]
106
+ stack << [:beginner]
107
+ end
108
+ start = s.pos - s.matched[/[^\n]+$/].size
109
+ name = m[1].sub(/::([^:.]+)$/, '.\1')
110
+ name.sub!(/#{_stack.last[1]}\./, 'self.') if _stack.any?
111
+ if name[/^self\.(.+)/]
112
+ stack << [:def, "#{_stack.lasts*'::'}.#$1", start]
113
+ elsif name['.'] and name =~ /^[A-Z]/
114
+ mod, name = name/'.'
115
+ fix_module_name(_stack.lasts*'::', mod) >> '.' >> name
116
+ stack << [:def, name, start]
117
+ else
118
+ prefix = (_stack.any? && _stack[-1][0] == :singleton) ? _stack[-1][1]+'.' : _stack.lasts*'::'+'#'
119
+ stack << [:def, prefix+name, start]
120
+ end
121
+ end
122
+
123
+ def self::Alias(s, m)
124
+ debug
125
+ _stack = clean_stack
126
+ case _stack.any? && _stack[-1][0]
127
+ when false, :def, :block
128
+ return
129
+ when :singleton
130
+ prefix = _stack[-1][1]
131
+ new, old = '.'+m[1], '.'+m[2]
132
+ else
133
+ prefix = _stack.lasts*'::'
134
+ new, old = '#'+m[1], '#'+m[2]
135
+ end
136
+ MethodCache[prefix][new] = MethodCache[prefix][old] || "def #{new}(*args)\n #{old}(*args)\nend"
137
+ end
138
+
139
+ end if !defined? Defaults
140
+
141
+ Closers = {'<' => '>', '{' => '}', '[' => ']', '(' => ')'}
142
+
143
+ def init_instructions
144
+ [
145
+ [/^\#/, lambda {|s, m| s.scan_until(/\n/)}],
146
+ [@Processor::StringRE, method(:string)],
147
+
148
+ [/^\{$/, lambda {|s, m|
149
+ debug
150
+ $log.debug @curls_count
151
+ @curls_count += 1
152
+ }],
153
+
154
+ [/^\}$/, method(:curl_close)],
155
+ [@Processor::Ord],
156
+
157
+ [@Processor::BlockOpen, lambda {|s, m|
158
+ debug
159
+ $log.debug @curls_count
160
+ @stack << [:block]
161
+ }],
162
+
163
+ [@Processor::ModDef, @Processor.method(:Module)],
164
+ [@Processor::ClassDef, @Processor.method(:Class)],
165
+ [@Processor::MethodDef, @Processor.method(:Method)],
166
+ [@Processor::AliasDef, @Processor.method(:Alias)],
167
+ [@Processor::Symbol],
168
+ [@Processor::RERE, method(:string)],
169
+ [@Processor::HeredocRE, method(:heredoc)],
170
+
171
+ [/(^|\n)=begin/, lambda {|s, m| s.scan_until(/\n=end\s*\n/m)}],
172
+
173
+ [@Processor::Attrs, lambda {|s, m|
174
+ attr_accessors s, m
175
+ if s.matched =~ / end$/
176
+ end!(s)
177
+ elsif s.matched =~ /[^\?]}$/
178
+ curl_close
179
+ end
180
+ }],
181
+
182
+ [@Processor::Include, lambda {|s, m|
183
+ _stack = clean_stack
184
+ if _stack.empty?
185
+ if m[1] == 'include'
186
+ inherit! 'Object', m[2]
187
+ else
188
+ inherit_singletons! 'Object', m[2]
189
+ end
190
+ elsif !_stack[-1][0].in([:def, :block]) and m[2] =~ /^[A-Z]/
191
+ if m[1] == 'include'
192
+ inherit! _stack.lasts*'::', m[2]
193
+ else
194
+ inherit_singletons! _stack.lasts*'::', m[2]
195
+ end
196
+ end
197
+ }],
198
+
199
+ [@Processor::AliasMethod, lambda {|s, m|
200
+ _stack = clean_stack
201
+ if _stack[-1][0] == :class
202
+ new, old = m[1..2]
203
+ prefix = _stack.lasts*'::'
204
+ @MethodCache[prefix][new] = @MethodCache[prefix][old] || "def #{new}(*args)\n #{old}(*args)\nend"
205
+ end
206
+ }],
207
+
208
+ [@Processor::Beginners, lambda {|s, m|
209
+ debug
210
+ $log.debug [m, s.last, s.string[s.last-1,1].to_s]
211
+ if (m[2] and s.last != 0 and m[2].tr(' \t', '').empty? and !(s.string[s.last-1,1].to_s)[/[\n;({\[]/])
212
+ else
213
+ if m[3] == 'if' and @stack.empty? and s.check_until(@Processor::EOF) and s.matched != "\n"
214
+ throw :EOF
215
+ end
216
+ @stack << [m[5] ? :block : :beginner]
217
+ end
218
+ }],
219
+
220
+ [/(^|[\s;])end.?/, method(:end!)],
221
+ [/(^|[\s;])__END__/, lambda {|s, m| throw :EOF}]
222
+ ].dup
223
+ end
224
+
225
+ def debug(s,m)
226
+ $log.debug(:caller=>2) {"#{s.string[0, s.pos].count("\n")+1}:#{s.head.size + s.matched_size - ((s.head+s.matched).reverse.index("\n") || 0)}"}
227
+ $log.debug(@stack, :caller=>2)
228
+ $log.debug(:caller=>2) {Painter.g(s.head+s.matched)}
229
+ end
230
+
231
+ def initialize(instructions_module=Defaults)
232
+ @MethodCache = {'Object' => {}}
233
+ @ReadPaths = {}
234
+ @Processor = instructions_module
235
+ @Instructions = init_instructions
236
+ @MainParseRE = @Processor::MainParseRE
237
+ add_method_seeker('get_opts') {|s, args| $log << args}
238
+ end
239
+
240
+ def add_instruction(re, &callback)
241
+ @MainParseRE |= re
242
+ @Instructions << [re, callback]
243
+ end
244
+
245
+ def add_method_seeker(name, *args, &callback)
246
+ pattern = /(?:^|[\s#{@Processor::Leftop}])(#{name})[( ] */
247
+ add_instruction(pattern) {|s, m|
248
+ yield s, arguments(s, m)
249
+ }
250
+ end
251
+
252
+ # Parser methods
253
+
254
+ def arguments(s, m)
255
+ arg_list = [m[1]]
256
+ counts = {'}' => 0, ']' => 0, ')' => 0}
257
+ catch(:EOL) {s.each(@Processor::ArgumentsParseRE, [
258
+ [/^[{\[(]$/, lambda {|s, m| counts[Closers[m[0]]] += 1}],
259
+ [/^[}\])]$/, lambda {|s, m|
260
+ if counts[m[0]] > 0
261
+ counts[m[0]] -= 1
262
+ else
263
+ curl_close
264
+ throw :EOL
265
+ end
266
+ }],
267
+ [/^[,\|]$/, lambda {|s, m| s.scan_until(/\s*/m)}],
268
+ [/[#\n;]| end\b/, lambda {|s, m|
269
+ s.scan_until(/\n/) if m[0] == '#'
270
+ heredoc_list.each {|opener| string(s, [nil]*4+opener)}
271
+ throw :EOL
272
+ }],
273
+ [@Processor::StringRE, lambda {|s, m|
274
+ str = [s.pos-1, string(s, m)]
275
+ str[1] ? arg_list << s.string[str[0]...str[1]] : arg_list << s.string[str[0]-1..str[0]]
276
+ }],
277
+ [@Processor::RERE, lambda {|s, m|
278
+ str = [s.pos-1, string(s, m)]
279
+ arg_list << s.string[str[0]...str[1]]
280
+ }],
281
+ [/^ +[:?]\s+$/],
282
+ [/^ *=>\s*$/, lambda {|s, m| arg_list << '=>'}],
283
+ [/^[:@$.\w!?]+([( ] *)?$/, lambda {|s, m|
284
+ str = [s.pos-s.matched_size, arguments(s, m) && s.pos]
285
+ arg_list << s.string[str[0]...str[1]] if str[1]
286
+ }]
287
+ ])}
288
+ arg_list
289
+ end
290
+
291
+ def string(s, m)
292
+ debug
293
+ return if m[1] and s.- == '$'
294
+ opener = m[1] || m[3] || m[5]
295
+ $log.log {"entering #{opener}-quotes, matched as '#{Painter.g s.matched}' at #{s.string[0..s.pos].count("\n")+1}"}
296
+ if opener == m[5]
297
+ closer = opener = m[5].tr('`\'"', '')
298
+ quote_re = /\\|\n#{'\s*' if m[4]}#{closer}/
299
+ else
300
+ closer = Closers[opener] || opener
301
+ quote_re = /\\|#{Regexp.escape closer}/
302
+ end
303
+ openers_cnt = 1
304
+ inner_curls_count = 0
305
+ backslash = false
306
+ quote_re |= /#\{/ if (m[5] and m[5].ord != ?') or closer =~ /[\/"`]/ or (m[2] =~ /[xrQW]/ or m[3])
307
+ instructions = [
308
+ [@Processor::Ord],
309
+ [/\s*#{Regexp.escape closer}$/, lambda {|s, m|
310
+ if backslash
311
+ backslash = false
312
+ break if s.- == '\\' and m[0] == closer
313
+ end
314
+ if (openers_cnt -= 1) == 0
315
+ $log.log {"exiting through #{closer}-quotes at #{s.string[0...s.pos].count("\n")+1}"}
316
+ throw :EOS
317
+ else
318
+ $log.log 'decreasing openers count'
319
+ end
320
+ }],
321
+ [/\\/, lambda {|s, m|
322
+ prev = s.-
323
+ backslash = true
324
+ if prev == '\\'
325
+ i = 2
326
+ while prev == '\\'
327
+ prev = s.prev_in i
328
+ i += 1
329
+ backslash = !backslash
330
+ end
331
+ end
332
+ $log.log {"#{!backslash ? 'closed' : 'found'} \\ in #{opener}-quotes at #{s.string[0, s.pos].count("\n")+1}"}
333
+ }],
334
+ [/\#\{/, lambda {|s, m|
335
+ if backslash
336
+ backslash = false
337
+ if s.- == '\\'
338
+ openers_cnt += 1 if closer == '}'
339
+ break
340
+ end
341
+ end
342
+ $log.log "entering curls"
343
+ inner_curls_count += 1
344
+ catch(:inner_out) {s.each(@Processor::StringParseRE, [
345
+ [/^\#$/, lambda {|s, m|
346
+ $log.log 'reading comment'
347
+ s.scan_until(/\n/)}],
348
+ [/^\{$/, lambda {|s, m|
349
+ $log.log "increasing curls count"
350
+ inner_curls_count += 1}],
351
+ [/^\}$/, lambda {|s, m|
352
+ if (inner_curls_count -= 1) == 0
353
+ $log.log "exiting curls"
354
+ throw :inner_out
355
+ else
356
+ $log.log "decreasing curls count"
357
+ end}],
358
+ [@Processor::HeredocRE, method(:heredoc)],
359
+ [@Processor::StringRE, method(:string)],
360
+ [@Processor::RERE, method(:string)]
361
+ ])}
362
+ }]
363
+ ]
364
+ if closer != opener
365
+ quote_re |= /#{Regexp.escape opener}/
366
+ instructions << [/#{Regexp.escape opener}$/, lambda {|s, m|
367
+ if backslash
368
+ backslash = false
369
+ break if s.- == '\\'
370
+ end
371
+ $log.log 'increasing openers count'
372
+ openers_cnt += 1
373
+ }]
374
+ $log.debug [quote_re,instructions]
375
+ end
376
+
377
+ catch(:EOS) {s.each(quote_re, instructions)}
378
+ s.pos
379
+ end
380
+
381
+ def heredoc(s=nil, m)
382
+ heredoc_list = [m[1..2]]
383
+ catch(:EOL) {s.each(@Processor::HeredocParseRE, [
384
+ [/[#\n]/, lambda {|s, m|
385
+ s.scan_until(/\n/) if m[0] == '#'
386
+ heredoc_list.each {|opener| string(s, [nil]*4+opener)}
387
+ throw :EOL
388
+ }],
389
+ [@Processor::HeredocRE, lambda {|s, m| heredoc_list << m[1..2]}],
390
+ [@Processor::StringRE, method(:string)],
391
+ [@Processor::RERE, method(:string)]
392
+ ])}
393
+ end
394
+
395
+ def curl_close(*)
396
+ if @curls_count == 0
397
+ @stack.pop
398
+ else
399
+ @curls_count -= 1
400
+ end
401
+ end
402
+
403
+ def end!(s, *)
404
+ debug
405
+ if s.+ !~ /[?!(]/
406
+ exit = @stack.pop
407
+ case exit[0]
408
+ when :def
409
+ prefix, name = exit[1].sharp_split(/[.@#]/, 2)
410
+ if !name
411
+ prefix, name = 'Object', prefix
412
+ end
413
+ if @MethodCache[prefix]
414
+ (@MethodCache[prefix][name] ||= []) << (path.inline ? [path, exit[2]...s.pos] : s.string[exit[2]...s.pos])
415
+ end
416
+ end
417
+ end
418
+ end
419
+
420
+ def attr_accessors(s, m)
421
+ _stack = clean_stack
422
+ if _stack[-1][0] == :class
423
+ prefix = _stack.lasts*'::'
424
+ attrs = (m[3]/',').map {|attr| (m[1] ? '.' : '#')+attr.strip[1..-1]}
425
+ if m[2].in %w(reader accessor)
426
+ attrs.each {|attr| (@MethodCache[prefix][attr] ||= []) << "def #{'self.' if m[1]}#{attr}\n #{'@' if m[1]}@#{attr}\nend"}
427
+ end
428
+ if m[2].in %w(writer accessor)
429
+ attrs.each {|attr| (@MethodCache[prefix][attr] ||= []) << "def #{'self.' if m[1]}#{attr}=value\n #{'@' if m[1]}@#{attr} = value\nend"}
430
+ end
431
+ end
432
+ end
433
+
434
+ def parse_file(path)
435
+ @stack = []
436
+
437
+ if path.inline
438
+ return if @ReadPaths[path]
439
+ lines = get_lines(path)[0]
440
+ @ReadPaths[path] = true
441
+ else
442
+ lines = path.sharp_split(/\n/)
443
+ end
444
+ if RUBY_VERSION > '1.9'
445
+ ss = StringScanner lines.join.force_encoding('UTF-8')
446
+ else
447
+ ss = StringScanner lines.join
448
+ end
449
+
450
+ @curls_count = 0
451
+ catch(:EOF) { ss.each @MainParseRE, @Instructions }
452
+ raise "Can't parse: #{@stack.inspect}, #{ss.string[ss.last..ss.pos].inspect}" if @stack.any?
453
+ ss
454
+ end
455
+
456
+ def clean_stack(no_def=false)
457
+ @stack.select {|e| e[0] != :beginner and !no_def || e[0] != :def}
458
+ end
459
+
460
+ def inherit!(descendant, ancestor)
461
+ @MethodCache[descendant].reverse_merge((
462
+ @MethodCache[fix_module_name(descendant, ancestor)] ||= {}
463
+ ).map_values {|defs| defs.dup})
464
+ end
465
+
466
+ def inherit_singletons!(descendant, ancestor)
467
+ (@MethodCache[fix_module_name(descendant, ancestor)] ||= {}).each {|name, defs|
468
+ @MethodCache[descendant][name.sub('#', '.')] = defs.dup if name.ord == ?#
469
+ }
470
+ end
471
+
472
+ def fix_module_name(current, name)
473
+ if name =~ /^::/ or current == ''
474
+ current+name
475
+ elsif name == current or name == 'self'
476
+ current
477
+ elsif name !~ /^[A-Z]/
478
+ current+'#'+name
479
+ else
480
+ path = current+'::'+name
481
+ if @MethodCache[path]
482
+ path
483
+ else
484
+ @MethodCache[name] ||= {}
485
+ name
486
+ end
487
+ end
488
+ end
489
+
490
+ def get_lines(path)
491
+ SCRIPT_LINES__.to_a.select {|d, f| d[path]}.to_a.lasts
492
+ end
493
+
494
+ def print_lines(prefix, name, all)
495
+ map = lambda {|lines|
496
+ if lines.is Array
497
+ lines = SCRIPT_LINES__[lines[0]].join[lines[1]]
498
+ end
499
+ lines }
500
+ methods = all ? @MethodCache[prefix][name].map(&map) : map.call(@MethodCache[prefix][name].last)
501
+ puts methods
502
+ end
503
+
504
+ def code_of(path, name=nil, all=false)
505
+ if name.in [true, :all]
506
+ all = true
507
+ end
508
+ if path.is String
509
+ prefix, name = path.sharp_split(/[.#]/, 2)
510
+ elsif Class === path
511
+ prefix = path.name
512
+ name = ".#{name}"
513
+ else
514
+ prefix = path.class.name
515
+ name = "##{name}"
516
+ end
517
+ if !(@MethodCache[prefix]||{})[name]
518
+ puts "looking up script lines, please wait..."
519
+ SCRIPT_LINES__.each_key {|k| parse_file k
520
+ break if (@MethodCache[prefix]||{})[name]
521
+ }
522
+ end
523
+ if !(@MethodCache[prefix]||{})[name]
524
+ print "nothing was found for #{prefix}#{name}"
525
+ name = name.tr('#.', '.#')
526
+ if (@MethodCache[prefix]||{})[name]
527
+ puts ", but found for #{name}:"
528
+ print_lines prefix, name, all
529
+ else puts ''
530
+ end
531
+ else
532
+ puts "code for #{prefix}#{name}:"
533
+ print_lines prefix, name, all
534
+ end
535
+ end
536
+
537
+ end
538
+
539
+ Reader = CodeReader.new
540
+ end
541
+
542
+ module Kernel
543
+
544
+ def code_of(*args)
545
+ RMTools::Reader.code_of(*args)
546
+ end
547
+
548
+ end
549
+
550
+ class Method
551
+
552
+ def code(all=false)
553
+ RMTools::Reader.code_of(receiver, name, all)
554
+ end
555
+
556
+ end