rbx-linecache 0.44-universal-rubinius

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