rbx-linecache 0.44-universal-rubinius

Sign up to get free protection for your applications and to get access to all the features.
data/lib/linecache.rb ADDED
@@ -0,0 +1,498 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (C) 2007, 2008, 2010 Rocky Bernstein <rockyb@rubyforge.net>
3
+ #
4
+ # This program is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation; either version 2 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17
+ # 02110-1301 USA.
18
+ #
19
+
20
+ # Author:: Rocky Bernstein (mailto:rockyb@rubyforge.net)
21
+ #
22
+ # = linecache
23
+ # A module to read and cache lines of a Ruby program.
24
+
25
+ # == SYNOPSIS
26
+ #
27
+ # The LineCache module allows one to get any line from any file,
28
+ # caching lines of the file on first access to the file. Although the
29
+ # file may be any file, the common use is when the file is a Ruby
30
+ # script since parsing of the file is done to figure out where the
31
+ # statement boundaries are.
32
+ #
33
+ # The routines here may be is useful when a small random sets of lines
34
+ # are read from a single file, in particular in a debugger to show
35
+ # source lines.
36
+ #
37
+ #
38
+ # require 'linecache'
39
+ # lines = LineCache::getlines('/tmp/myruby.rb')
40
+ # # The following lines have same effect as the above.
41
+ # $: << '/tmp'
42
+ # Dir.chdir('/tmp') {lines = LineCache::getlines('myruby.rb')
43
+ #
44
+ # line = LineCache::getline('/tmp/myruby.rb', 6)
45
+ # # Note lines[6] == line (if /tmp/myruby.rb has 6 lines)
46
+ #
47
+ # LineCache::clear_file_cache
48
+ # LineCache::clear_file_cache('/tmp/myruby.rb')
49
+ # LineCache::update_cache # Check for modifications of all cached files.
50
+ #
51
+ # Some parts of the interface is derived from the Python module of the
52
+ # same name.
53
+ #
54
+
55
+ require 'tempfile'
56
+ require 'digest/sha1'
57
+ require 'set'
58
+
59
+ require 'rubygems'
60
+ require 'require_relative'
61
+ require_relative 'tracelines'
62
+
63
+ # = module LineCache
64
+ # A module to read and cache lines of a Ruby program.
65
+ module LineCache
66
+ VERSION = '0.44'
67
+ LineCacheInfo = Struct.new(:stat, :line_numbers, :lines, :path, :sha1) unless
68
+ defined?(LineCacheInfo)
69
+
70
+ # The file cache. The key is a name as would be given by Ruby for
71
+ # __FILE__. The value is a LineCacheInfo object.
72
+ @@file_cache = {}
73
+ @@script_cache = {}
74
+
75
+ # Maps a string filename (a String) to a key in @@file_cache (a
76
+ # String).
77
+ #
78
+ # One important use of @@file2file_remap is mapping the a full path
79
+ # of a file into the name stored in @@file_cache or given by Ruby's
80
+ # __FILE__. Applications such as those that get input from users,
81
+ # may want canonicalize a file name before looking it up. This map
82
+ # gives a way to do that.
83
+ #
84
+ # Another related use is when a template system is used. Here we'll
85
+ # probably want to remap not only the file name but also line
86
+ # ranges. Will probably use this for that, but I'm not sure.
87
+ @@file2file_remap = {}
88
+ @@file2file_remap_lines = {}
89
+
90
+ @@script2file = {}
91
+
92
+ def remove_script_temps
93
+ @@script2file.values.each do |filename|
94
+ File.unlink(filename)
95
+ end
96
+ end
97
+ module_function :remove_script_temps
98
+ at_exit { remove_script_temps }
99
+
100
+
101
+ # Clear the file cache entirely.
102
+ def clear_file_cache()
103
+ @@file_cache = {}
104
+ @@file2file_remap = {}
105
+ @@file2file_remap_lines = {}
106
+ end
107
+ module_function :clear_file_cache
108
+
109
+ # Clear the script cache entirely.
110
+ def clear_script_cache()
111
+ @@script_cache = {}
112
+ end
113
+ module_function :clear_file_cache
114
+
115
+ # Return an array of cached file names
116
+ def cached_files()
117
+ @@file_cache.keys
118
+ end
119
+ module_function :cached_files
120
+
121
+ # Discard cache entries that are out of date. If +filename+ is +nil+
122
+ # all entries in the file cache +@@file_cache+ are checked.
123
+ # If we don't have stat information about a file, which can happen
124
+ # if the file was read from __SCRIPT_LINES but no corresponding file
125
+ # is found, it will be kept. Return a list of invalidated filenames.
126
+ # nil is returned if a filename was given but not found cached.
127
+ def checkcache(filename=nil, use_script_lines=false)
128
+
129
+ if !filename
130
+ filenames = @@file_cache.keys()
131
+ elsif @@file_cache.member?(filename)
132
+ filenames = [filename]
133
+ else
134
+ return nil
135
+ end
136
+
137
+ result = []
138
+ for filename in filenames
139
+ next unless @@file_cache.member?(filename)
140
+ path = @@file_cache[filename].path
141
+ if File.exist?(path)
142
+ cache_info = @@file_cache[filename].stat
143
+ stat = File.stat(path)
144
+ if cache_info
145
+ if stat &&
146
+ (cache_info.size != stat.size or cache_info.mtime != stat.mtime)
147
+ result << filename
148
+ update_cache(filename, use_script_lines)
149
+ end
150
+ else
151
+ result << filename
152
+ update_cache(filename, use_script_lines)
153
+ end
154
+ end
155
+ end
156
+ return result
157
+ end
158
+ module_function :checkcache
159
+
160
+ # Cache script if it's not already cached.
161
+ def cache_script(script, string=nil, sha1=nil)
162
+ if !@@script_cache.member?(script)
163
+ update_script_cache(script, string, sha1)
164
+ end
165
+ script
166
+ end
167
+ module_function :cache_script
168
+
169
+ # Cache file name or script object if it's not already cached.
170
+ # Return the expanded filename for it in the cache if a filename,
171
+ # or the script, or nil if we can't find the file.
172
+ def cache(file_or_script, reload_on_change=false)
173
+ if file_or_script.kind_of?(String)
174
+ cache_file(file_or_script, reload_on_change)
175
+ else
176
+ cache_script(file_or_script)
177
+ end
178
+ end
179
+ module_function :cache
180
+
181
+ # Cache filename if it's not already cached.
182
+ # Return the expanded filename for it in the cache
183
+ # or nil if we can't find the file.
184
+ def cache_file(filename, reload_on_change=false)
185
+ if @@file_cache.member?(filename)
186
+ checkcache(filename) if reload_on_change
187
+ else
188
+ update_cache(filename, true)
189
+ end
190
+ if @@file_cache.member?(filename)
191
+ @@file_cache[filename].path
192
+ else
193
+ nil
194
+ end
195
+ end
196
+ module_function :cache_file
197
+
198
+ # Return true if file_or_script is cached
199
+ def cached?(file_or_script)
200
+ if file_or_script.kind_of?(String)
201
+ @@file_cache.member?(map_file(file_or_script))
202
+ else
203
+ cached_script?(file_or_script)
204
+ end
205
+ end
206
+ module_function :cached?
207
+
208
+ def cached_script?(script)
209
+ @@script_cache.member?(script)
210
+ end
211
+ module_function :cached_script?
212
+
213
+ def empty?(filename)
214
+ filename=map_file(filename)
215
+ @@file_cache[filename].lines.empty?
216
+ end
217
+ module_function :empty?
218
+
219
+ # Get line +line_number+ from file named +filename+. Return nil if
220
+ # there was a problem. If a file named filename is not found, the
221
+ # function will look for it in the $: array.
222
+ #
223
+ # Examples:
224
+ #
225
+ # lines = LineCache::getline('/tmp/myfile.rb')
226
+ # # Same as above
227
+ # $: << '/tmp'
228
+ # lines = LineCache.getlines('myfile.rb')
229
+ #
230
+ def getline(file_or_script, line_number, reload_on_change=true)
231
+ lines =
232
+ if file_or_script.kind_of?(String)
233
+ filename = map_file(file_or_script)
234
+ filename, line_number = map_file_line(filename, line_number)
235
+ getlines(filename, reload_on_change)
236
+ else
237
+ script_getlines(file_or_script)
238
+ end
239
+ if lines and (1..lines.size) === line_number
240
+ return lines[line_number-1]
241
+ else
242
+ return nil
243
+ end
244
+ end
245
+ module_function :getline
246
+
247
+ # Read lines of +script+ and cache the results. However +script+ was
248
+ # previously cached use the results from the cache. Return nil
249
+ # if we can't get lines
250
+ def script_getlines(script)
251
+ if @@script_cache.member?(script)
252
+ return @@script_cache[script].lines
253
+ else
254
+ update_script_cache(script)
255
+ if @@script_cache.member?(script)
256
+ return @@script_cache[script].lines
257
+ else
258
+ return nil
259
+ end
260
+ end
261
+ end
262
+ module_function :script_getlines
263
+
264
+ # Read lines of +filename+ and cache the results. However +filename+ was
265
+ # previously cached use the results from the cache. Return nil
266
+ # if we can't get lines
267
+ def getlines(filename, reload_on_change=false)
268
+ filename = map_file(filename)
269
+ checkcache(filename) if reload_on_change
270
+ if @@file_cache.member?(filename)
271
+ return @@file_cache[filename].lines
272
+ else
273
+ update_cache(filename, true)
274
+ if @@file_cache.member?(filename)
275
+ return @@file_cache[filename].lines
276
+ else
277
+ return nil
278
+ end
279
+ end
280
+ end
281
+ module_function :getlines
282
+
283
+ # Return full filename path for filename
284
+ def path(filename)
285
+ return unless filename.kind_of?(String)
286
+ filename = map_file(filename)
287
+ return nil unless @@file_cache.member?(filename)
288
+ @@file_cache[filename].path
289
+ end
290
+ module_function :path
291
+
292
+ def remap_file(to_file, from_file)
293
+ @@file2file_remap[to_file] = from_file
294
+ end
295
+ module_function :remap_file
296
+
297
+ def remap_file_lines(from_file, to_file, range, start)
298
+ range = (range..range) if range.kind_of?(Fixnum)
299
+ to_file = from_file unless to_file
300
+ if @@file2file_remap_lines[to_file]
301
+ # FIXME: need to check for overwriting ranges: whether
302
+ # they intersect or one encompasses another.
303
+ @@file2file_remap_lines[to_file] << [from_file, range, start]
304
+ else
305
+ @@file2file_remap_lines[to_file] = [[from_file, range, start]]
306
+ end
307
+ end
308
+ module_function :remap_file_lines
309
+
310
+ # Return SHA1 of filename.
311
+ def sha1(filename)
312
+ filename = map_file(filename)
313
+ return nil unless @@file_cache.member?(filename)
314
+ return @@file_cache[filename].sha1.hexdigest if
315
+ @@file_cache[filename].sha1
316
+ sha1 = Digest::SHA1.new
317
+ @@file_cache[filename].lines.each do |line|
318
+ sha1 << line + "\n"
319
+ end
320
+ @@file_cache[filename].sha1 = sha1
321
+ sha1.hexdigest
322
+ end
323
+ module_function :sha1
324
+
325
+ # Return the number of lines in filename
326
+ def size(file_or_script)
327
+ cache(file_or_script)
328
+ if file_or_script.kind_of?(String)
329
+ file_or_script = map_file(file_or_script)
330
+ return nil unless @@file_cache.member?(file_or_script)
331
+ @@file_cache[file_or_script].lines.length
332
+ else
333
+ return nil unless @@script_cache.member?(file_or_script)
334
+ @@script_cache[file_or_script].lines.length
335
+ end
336
+ end
337
+ module_function :size
338
+
339
+ # Return File.stat in the cache for filename.
340
+ def stat(filename)
341
+ return nil unless @@file_cache.member?(filename)
342
+ @@file_cache[filename].stat
343
+ end
344
+ module_function :stat
345
+
346
+ # Return an Array of breakpoints in filename.
347
+ # The list will contain an entry for each distinct line event call
348
+ # so it is possible (and possibly useful) for a line number appear more
349
+ # than once.
350
+ def trace_line_numbers(filename, reload_on_change=false)
351
+ fullname = cache(filename, reload_on_change)
352
+ return nil unless fullname
353
+ e = @@file_cache[filename]
354
+ unless e.line_numbers
355
+ e.line_numbers =
356
+ TraceLineNumbers.lnums_for_str_array(e.lines)
357
+ e.line_numbers = false unless e.line_numbers
358
+ end
359
+ e.line_numbers
360
+ end
361
+ module_function :trace_line_numbers
362
+
363
+ def map_file(file)
364
+ @@file2file_remap[file] ? @@file2file_remap[file] : file
365
+ end
366
+ module_function :map_file
367
+
368
+ def map_script(script)
369
+ if @@script2file[script]
370
+ @@script2file[script]
371
+ else
372
+ # Doc says there's new takes an optional string parameter
373
+ # But it doesn't work for me
374
+ sha1 = Digest::SHA1.new
375
+ string = script.eval_source
376
+ sha1 << script.eval_source
377
+ tempfile = Tempfile.new(["eval-#{sha1.hexdigest[0...7]}-", '.rb'])
378
+ tempfile.open.puts(string)
379
+ tempfile.close
380
+ # cache_script(script, string, sha1.hexdigest)
381
+ @@script2file[script] = tempfile.path
382
+ tempfile.path
383
+ end
384
+ end
385
+ module_function :map_script
386
+
387
+ def map_file_line(file, line)
388
+ if @@file2file_remap_lines[file]
389
+ @@file2file_remap_lines[file].each do |from_file, range, start|
390
+ if range === line
391
+ from_file = from_file || file
392
+ return [from_file, start+line-range.begin]
393
+ end
394
+ end
395
+ end
396
+ return [map_file(file), line]
397
+ end
398
+ module_function :map_file_line
399
+
400
+ def script_is_eval?(script)
401
+ !!script.eval_source
402
+ end
403
+ module_function :script_is_eval?
404
+
405
+ # Update a cache entry. If something is wrong, return nil. Return
406
+ # true if the cache was updated and false if not.
407
+ def update_script_cache(script, string=nil, sha1=nil)
408
+ return false unless script_is_eval?(script)
409
+ string = script.eval_source unless string
410
+ @@script_cache[script] =
411
+ LineCacheInfo.new(nil, nil, string.split(/\n/), nil, sha1)
412
+ return true
413
+ end
414
+ module_function :update_script_cache
415
+
416
+ # Update a cache entry. If something's
417
+ # wrong, return nil. Return true if the cache was updated and false
418
+ # if not. If use_script_lines is true, use that as the source for the
419
+ # lines of the file
420
+ def update_cache(filename, use_script_lines=false)
421
+
422
+ return nil unless filename
423
+
424
+ @@file_cache.delete(filename)
425
+ path = File.expand_path(filename)
426
+
427
+ if File.exist?(path)
428
+ stat = File.stat(path)
429
+ elsif File.basename(filename) == filename
430
+ # try looking through the search path.
431
+ stat = nil
432
+ for dirname in $:
433
+ path = File.join(dirname, filename)
434
+ if File.exist?(path)
435
+ stat = File.stat(path)
436
+ break
437
+ end
438
+ end
439
+ return false unless stat
440
+ end
441
+ begin
442
+ fp = File.open(path, 'r')
443
+ lines = fp.readlines()
444
+ fp.close()
445
+ rescue
446
+ ## print '*** cannot open', path, ':', msg
447
+ return nil
448
+ end
449
+ @@file_cache[filename] = LineCacheInfo.new(File.stat(path), nil, lines,
450
+ path, nil)
451
+ @@file2file_remap[path] = filename
452
+ return true
453
+ end
454
+
455
+ module_function :update_cache
456
+ end
457
+
458
+ # example usage
459
+ if __FILE__ == $0
460
+ def yes_no(var)
461
+ return var ? "" : "not "
462
+ end
463
+
464
+ lines = LineCache::getlines(__FILE__)
465
+ puts "#{__FILE__} has #{LineCache.size(__FILE__)} lines"
466
+ line = LineCache::getline(__FILE__, 6)
467
+ puts "The 6th line is\n#{line}"
468
+ line = LineCache::remap_file(__FILE__, 'another_name')
469
+ puts LineCache::getline('another_name', 7)
470
+
471
+ puts("Files cached: #{LineCache::cached_files.inspect}")
472
+ LineCache::update_cache(__FILE__)
473
+ LineCache::checkcache(__FILE__)
474
+ puts "#{__FILE__} has #{LineCache::size(__FILE__)} lines"
475
+ puts "#{__FILE__} trace line numbers:\n" +
476
+ "#{LineCache::trace_line_numbers(__FILE__).to_a.sort.inspect}"
477
+ puts("#{__FILE__} is %scached." %
478
+ yes_no(LineCache::cached?(__FILE__)))
479
+ puts LineCache::stat(__FILE__).inspect
480
+ puts "Full path: #{LineCache::path(__FILE__)}"
481
+ LineCache::checkcache # Check all files in the cache
482
+ LineCache::clear_file_cache
483
+ puts("#{__FILE__} is now %scached." %
484
+ yes_no(LineCache::cached?(__FILE__)))
485
+ # digest = SCRIPT_LINES__.select{|k,v| k =~ /digest.rb$/}
486
+ # puts digest.first[0] if digest
487
+ line = LineCache::getline(__FILE__, 7)
488
+ puts "The 7th line is\n#{line}"
489
+ LineCache::remap_file_lines(__FILE__, 'test2', (10..20), 6)
490
+ puts LineCache::getline('test2', 10)
491
+ puts "Remapped 10th line of test2 is\n#{line}"
492
+ eval("loc = Rubinius::VM::backtrace(0)[0]
493
+ puts LineCache::getline(loc.static_scope.script, 1)")
494
+ eval("loc = Rubinius::VM::backtrace(0)[0]
495
+ puts LineCache::size(loc.static_scope.script)")
496
+ eval("loc = Rubinius::VM::backtrace(0)[0]
497
+ puts LineCache::map_script(loc.static_scope.script)")
498
+ end
data/lib/tracelines.rb ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module TraceLineNumbers
4
+ # Get line numbers for CompiledMethod.method#lines
5
+ # format is: ip line ip line ...
6
+ # Odd numbers then are the line numbers
7
+ def lnums_for_lines(lines)
8
+ odds = (0...lines.size/2).map{|i| i*2+1}
9
+ lines.to_a.values_at(*odds).sort
10
+ end
11
+ module_function :lnums_for_lines
12
+
13
+ def compiled_methods(cm)
14
+ result = [cm]
15
+ result += cm.child_methods.map{|child| compiled_methods(child)}.flatten
16
+ result
17
+ end
18
+ module_function :compiled_methods
19
+
20
+ def lnums_for_compiled_methods(compiled_method)
21
+ compiled_methods = compiled_methods(compiled_method)
22
+ compiled_methods.map { |cm| lnums_for_lines(cm.lines) }.flatten.sort
23
+ end
24
+ module_function :lnums_for_compiled_methods
25
+
26
+ # Return an array of lines numbers that could be
27
+ # stopped at given a file name of a Ruby program.
28
+ def lnums_for_file(file)
29
+ lnums_for_compiled_methods(Rubinius::Compiler.compile_file(file))
30
+ end
31
+ module_function :lnums_for_file
32
+
33
+ # Return an array of lines numbers that could be
34
+ # stopped at given a file name of a Ruby program.
35
+ # We assume the each line has \n at the end. If not
36
+ # set the newline parameters to \n.
37
+ def lnums_for_str_array(string_array, newline='')
38
+ str = string_array.join(newline)
39
+ lnums_for_str(str)
40
+ end
41
+ module_function :lnums_for_str_array
42
+
43
+ def lnums_for_str(str)
44
+ lnums_for_compiled_methods(Rubinius::Compiler.compile_string(str))
45
+ end
46
+
47
+ module_function :lnums_for_str
48
+ end
49
+
50
+ if __FILE__ == $0
51
+ # test_file = '../test/rcov-bug.rb'
52
+ test_file = File.join %W(#{File.dirname(__FILE__)}
53
+ ../test/data/begin1.rb)
54
+ puts TraceLineNumbers.lnums_for_file(test_file).inspect
55
+ test_file = File.join %W(#{File.dirname(__FILE__)}
56
+ ../test/data/def1.rb)
57
+ puts TraceLineNumbers.lnums_for_file(test_file).inspect
58
+ end
@@ -0,0 +1,3 @@
1
+ # [ 2 ]
2
+ begin
3
+ end
@@ -0,0 +1,3 @@
1
+ # [ 2 ]
2
+ begin begin end end
3
+
@@ -0,0 +1,6 @@
1
+ # [ 3 ]
2
+ begin
3
+ begin
4
+ end
5
+ end
6
+
@@ -0,0 +1,7 @@
1
+ # [2, 2, 3, 4, 6]
2
+ if true
3
+ puts '3'
4
+ puts '4'
5
+ end
6
+ if true
7
+ end
@@ -0,0 +1,4 @@
1
+ # [2, 2, 3, 4, 4]
2
+ def foo(&block)
3
+ end
4
+ foo{4}
@@ -0,0 +1,6 @@
1
+ # [ 2, 2, 3, 4, 6, 6 ]
2
+ case 2
3
+ when 3
4
+ when 4
5
+ else
6
+ end
@@ -0,0 +1,5 @@
1
+ # [ 3, 4, 5, 5 ]
2
+ case
3
+ when 3 != 3
4
+ when 4 != 4
5
+ end
@@ -0,0 +1,5 @@
1
+ # [2, 2, 3, 4, 5, 5]
2
+ case "2"
3
+ when Array
4
+ when Fixnum
5
+ end
@@ -0,0 +1,4 @@
1
+ # [2, 2, 3, 4]
2
+ case "2"
3
+ when Array, Fixnum
4
+ end
@@ -0,0 +1,10 @@
1
+ # [3, 4, 5, 6, 7, 9]
2
+ # Note: lines 5 and 7 won't appear in a trace.
3
+ case '3'
4
+ when '4'
5
+ x = 5
6
+ when '6'
7
+ x = 7
8
+ else
9
+ x = 9
10
+ end
@@ -0,0 +1,5 @@
1
+ # [3, 4]
2
+ class
3
+ A
4
+ class B ;
5
+ end end
@@ -0,0 +1,6 @@
1
+ # [ 1 ]
2
+
3
+ # space here and in next line
4
+
5
+
6
+
data/test/data/def1.rb ADDED
@@ -0,0 +1,9 @@
1
+ # [3, 3, 4, 4, 5, 7, 8, 8, 8, 9]
2
+ def
3
+ a ; end
4
+ def b
5
+ end
6
+ def c(
7
+ a=7,
8
+ b=8)
9
+ end
@@ -0,0 +1,3 @@
1
+ # [ 2, 2 ]
2
+ [2].each { }
3
+
data/test/data/end.rb ADDED
@@ -0,0 +1,3 @@
1
+ # [ 3, 3 ]
2
+ # POSTEXE
3
+ END { }
data/test/data/for1.rb ADDED
@@ -0,0 +1,4 @@
1
+ # [ 2, 2, 3 ]
2
+ for i in [2]
3
+ i
4
+ end
data/test/data/if1.rb ADDED
@@ -0,0 +1,4 @@
1
+ # [ 2 ]
2
+ x = true ? 1 : 2
3
+
4
+
data/test/data/if2.rb ADDED
@@ -0,0 +1,4 @@
1
+ # [ 2 ]
2
+ x = 2 if false
3
+
4
+
data/test/data/if3.rb ADDED
@@ -0,0 +1,9 @@
1
+ # [ 2, 3, 4, 4, 5, 6]
2
+ lines = ''
3
+ opts = {:print_source => true}
4
+ if opts[:print_source]
5
+ puts 5
6
+ puts lines
7
+ end
8
+
9
+