linecache 0.2 → 0.3
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/ChangeLog +259 -8
- data/NEWS +11 -1
- data/README +5 -2
- data/Rakefile +52 -6
- data/VERSION +1 -1
- data/ext/extconf.rb +16 -0
- data/ext/trace_nums.c +716 -0
- data/ext/trace_nums.h +111 -0
- data/ext/trace_nums.o +0 -0
- data/ext/trace_nums.so +0 -0
- data/lib/linecache.rb +212 -83
- data/lib/tracelines.rb +42 -0
- data/test/lnum-diag.rb +130 -0
- data/test/parse-show.rb +14 -0
- data/test/rcov-bug.rb +10 -0
- data/test/short-file +1 -1
- data/test/test-linecache.rb +66 -5
- data/test/test-lnum.rb +36 -0
- data/test/test-tracelines.rb +41 -0
- metadata +14 -2
data/ext/trace_nums.h
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
/* Order is in C enum order. The below is correct for Ruby 1.8.6.
|
2
|
+
Possibly others, but there may need some adjustment here.
|
3
|
+
*/
|
4
|
+
char *NODE2NAME[] =
|
5
|
+
{
|
6
|
+
"method",
|
7
|
+
"fbody",
|
8
|
+
"cfunc",
|
9
|
+
"scope",
|
10
|
+
"block",
|
11
|
+
"if",
|
12
|
+
"case",
|
13
|
+
"when",
|
14
|
+
"opt_n (-n)",
|
15
|
+
"while",
|
16
|
+
"until",
|
17
|
+
"iter",
|
18
|
+
"for",
|
19
|
+
"break",
|
20
|
+
"next",
|
21
|
+
"redo",
|
22
|
+
"retry",
|
23
|
+
"begin",
|
24
|
+
"rescue",
|
25
|
+
"resbody",
|
26
|
+
"ensure",
|
27
|
+
"and",
|
28
|
+
"or",
|
29
|
+
"not",
|
30
|
+
"masgn",
|
31
|
+
"lasgn (x=)",
|
32
|
+
"dasgn",
|
33
|
+
"dasgn_curr",
|
34
|
+
"gasgn",
|
35
|
+
"iasgn",
|
36
|
+
"cdecl",
|
37
|
+
"cvasgn",
|
38
|
+
"cvdecl",
|
39
|
+
"op_asgn1",
|
40
|
+
"op_asgn2",
|
41
|
+
"op_asgn_and",
|
42
|
+
"op_asgn_or",
|
43
|
+
"call",
|
44
|
+
"fcall",
|
45
|
+
"vcall",
|
46
|
+
"super",
|
47
|
+
"zsuper",
|
48
|
+
"array",
|
49
|
+
"zarray",
|
50
|
+
"hash",
|
51
|
+
"return",
|
52
|
+
"yield",
|
53
|
+
"lvar",
|
54
|
+
"dvar",
|
55
|
+
"gvar",
|
56
|
+
"ivar",
|
57
|
+
"const",
|
58
|
+
"cvar",
|
59
|
+
"nth_ref",
|
60
|
+
"back_ref",
|
61
|
+
"match",
|
62
|
+
"match2 (~=, !~)",
|
63
|
+
"match3 (~=, !~)",
|
64
|
+
"lit",
|
65
|
+
"str",
|
66
|
+
"dstr",
|
67
|
+
"xstr",
|
68
|
+
"dxstr",
|
69
|
+
"evstr",
|
70
|
+
"dregx",
|
71
|
+
"dregx_once",
|
72
|
+
"args",
|
73
|
+
"argscat",
|
74
|
+
"argspush",
|
75
|
+
"splat (*args)",
|
76
|
+
"to_ary",
|
77
|
+
"svalue",
|
78
|
+
"block_arg",
|
79
|
+
"block_pass",
|
80
|
+
"defn",
|
81
|
+
"defs",
|
82
|
+
"alias",
|
83
|
+
"valias",
|
84
|
+
"undef",
|
85
|
+
"class",
|
86
|
+
"module",
|
87
|
+
"sclass",
|
88
|
+
"colon2 (::)",
|
89
|
+
"colon3",
|
90
|
+
"cref",
|
91
|
+
"dot2 (..)",
|
92
|
+
"dot3 (...)",
|
93
|
+
"flip2",
|
94
|
+
"flip3",
|
95
|
+
"attrset",
|
96
|
+
"self",
|
97
|
+
"nil",
|
98
|
+
"true",
|
99
|
+
"false",
|
100
|
+
"defined?",
|
101
|
+
"newline (; or \\n)",
|
102
|
+
"postexe",
|
103
|
+
"alloca",
|
104
|
+
"dmethod",
|
105
|
+
"bmethod",
|
106
|
+
"memo",
|
107
|
+
"ifunc",
|
108
|
+
"dsym",
|
109
|
+
"attrasgn",
|
110
|
+
"last"
|
111
|
+
};
|
data/ext/trace_nums.o
ADDED
Binary file
|
data/ext/trace_nums.so
ADDED
Binary file
|
data/lib/linecache.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# $Id: linecache.rb
|
2
|
+
# $Id: linecache.rb 59 2008-01-28 15:35:34Z rockyb $
|
3
3
|
#
|
4
4
|
# Copyright (C) 2007, 2008 Rocky Bernstein <rockyb@rubyforge.net>
|
5
5
|
#
|
@@ -50,74 +50,65 @@
|
|
50
50
|
# same name.
|
51
51
|
#
|
52
52
|
|
53
|
-
require 'digest/sha1'
|
54
|
-
|
55
53
|
# Defining SCRIPT_LINES__ causes Ruby to cache the lines of files
|
56
54
|
# it reads. The key the setting of __FILE__ at the time when Ruby does
|
57
55
|
# its read. LineCache keeps a separate copy of the lines elsewhere
|
58
|
-
# and never destroys
|
56
|
+
# and never destroys SCRIPT_LINES__
|
59
57
|
SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
|
60
58
|
|
61
|
-
|
62
|
-
|
59
|
+
require 'digest/sha1'
|
60
|
+
require 'set'
|
61
|
+
|
62
|
+
begin require 'rubygems' rescue LoadError end
|
63
|
+
require 'tracelines'
|
64
|
+
# require 'ruby-debug' ; Debugger.start
|
63
65
|
|
64
66
|
# = module LineCache
|
65
67
|
# Module caching lines of a file
|
66
68
|
module LineCache
|
67
|
-
LineCacheInfo = Struct.new(:stat, :lines, :
|
69
|
+
LineCacheInfo = Struct.new(:stat, :line_numbers, :lines, :path, :sha1) unless
|
70
|
+
defined?(LineCacheInfo)
|
68
71
|
|
69
|
-
#
|
70
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
#
|
74
|
-
#
|
75
|
-
# lines = LineCache::getline('/tmp/myfile.rb)
|
76
|
-
# # Same as above
|
77
|
-
# $: << '/tmp'
|
78
|
-
# lines = Dir.chdir('/tmp') do
|
79
|
-
# lines = LineCache::getlines ('myfile.rb')
|
80
|
-
# end
|
72
|
+
# The file cache. The key is a name as would be given by Ruby for
|
73
|
+
# __FILE__. The value is a LineCacheInfo object.
|
74
|
+
@@file_cache = {}
|
75
|
+
|
76
|
+
# Maps a string filename (a String) to a key in @@file_cache (a
|
77
|
+
# String).
|
81
78
|
#
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
79
|
+
# One important use of @@file2file_remap is mapping the a full path
|
80
|
+
# of a file into the name stored in @@file_cache or given by Ruby's
|
81
|
+
# __FILE__. Applications such as those that get input from users,
|
82
|
+
# may want canonicalize a file name before looking it up. This map
|
83
|
+
# gives a way to do that.
|
84
|
+
#
|
85
|
+
# Another related use is when a template system is used. Here we'll
|
86
|
+
# probably want to remap not only the file name but also line
|
87
|
+
# ranges. Will probably use this for that, but I'm not sure.
|
88
|
+
@@file2file_remap = {}
|
89
|
+
@@file2file_remap_lines = {}
|
90
|
+
|
95
91
|
# Clear the file cache entirely.
|
96
92
|
def clear_file_cache()
|
97
93
|
@@file_cache = {}
|
94
|
+
@@file2file_remap = {}
|
95
|
+
@@file2file_remap_lines = {}
|
98
96
|
end
|
99
|
-
|
100
97
|
module_function :clear_file_cache
|
101
98
|
|
102
|
-
#
|
103
|
-
|
104
|
-
|
105
|
-
checkcache(filename) if reload_on_change
|
106
|
-
if @@file_cache.member?(filename)
|
107
|
-
return @@file_cache[filename].lines
|
108
|
-
else
|
109
|
-
return update_cache(filename, true)
|
110
|
-
end
|
99
|
+
# Return an array of cached file names
|
100
|
+
def cached_files()
|
101
|
+
@@file_cache.keys
|
111
102
|
end
|
112
|
-
|
113
|
-
module_function :getlines
|
103
|
+
module_function :cached_files
|
114
104
|
|
115
105
|
# Discard cache entries that are out of date. If +filename+ is +nil+
|
116
106
|
# all entries in the file cache +@@file_cache+ are checked.
|
117
107
|
# If we don't have stat information about a file which can happen
|
118
108
|
# if the file was read from __SCRIPT_LINES but no corresponding file
|
119
|
-
# is found, it will be kept.
|
120
|
-
|
109
|
+
# is found, it will be kept. Return a list of invalidated filenames.
|
110
|
+
# nil is returned if a filename was given but not found cached.
|
111
|
+
def checkcache(filename=nil, use_script_lines=false)
|
121
112
|
|
122
113
|
if !filename
|
123
114
|
filenames = @@file_cache.keys()
|
@@ -127,20 +118,21 @@ module LineCache
|
|
127
118
|
return nil
|
128
119
|
end
|
129
120
|
|
121
|
+
result = []
|
130
122
|
for filename in filenames
|
131
123
|
next unless @@file_cache.member?(filename)
|
132
|
-
|
133
|
-
if File.exist?(
|
124
|
+
path = @@file_cache[filename].path
|
125
|
+
if File.exist?(path)
|
134
126
|
cache_info = @@file_cache[filename]
|
135
|
-
stat = File.stat(
|
127
|
+
stat = File.stat(path)
|
136
128
|
if stat &&
|
137
129
|
(cache_info.size != stat.size or cache_info.mtime != stat.mtime)
|
138
|
-
|
130
|
+
result << filename
|
131
|
+
update_cache(filename, use_script_lines)
|
139
132
|
end
|
140
|
-
else
|
141
|
-
@@file_cache.delete(filename)
|
142
133
|
end
|
143
134
|
end
|
135
|
+
return result
|
144
136
|
end
|
145
137
|
module_function :checkcache
|
146
138
|
|
@@ -151,10 +143,10 @@ module LineCache
|
|
151
143
|
if @@file_cache.member?(filename)
|
152
144
|
checkcache(filename) if reload_on_change
|
153
145
|
else
|
154
|
-
|
146
|
+
update_cache(filename, true)
|
155
147
|
end
|
156
148
|
if @@file_cache.member?(filename)
|
157
|
-
@@file_cache[filename].
|
149
|
+
@@file_cache[filename].path
|
158
150
|
else
|
159
151
|
nil
|
160
152
|
end
|
@@ -163,12 +155,90 @@ module LineCache
|
|
163
155
|
|
164
156
|
# Return true if filename is cached
|
165
157
|
def cached?(filename)
|
166
|
-
@@file_cache.member?(filename)
|
158
|
+
@@file_cache.member?(unmap_file(filename))
|
167
159
|
end
|
168
160
|
module_function :cached?
|
161
|
+
|
162
|
+
def cached_script?(filename)
|
163
|
+
SCRIPT_LINES__.member?(unmap_file(filename))
|
164
|
+
end
|
165
|
+
module_function :cached_script?
|
169
166
|
|
167
|
+
def empty?(filename)
|
168
|
+
filename=unmap_file(filename)
|
169
|
+
@@file_cache[filename].lines.empty?
|
170
|
+
end
|
171
|
+
module_function :empty?
|
172
|
+
|
173
|
+
# Get line +line_number+ from file named +filename+. Return nil if
|
174
|
+
# there was a problem. If a file named filename is not found, the
|
175
|
+
# function will look for it in the $: path array.
|
176
|
+
#
|
177
|
+
# Examples:
|
178
|
+
#
|
179
|
+
# lines = LineCache::getline('/tmp/myfile.rb)
|
180
|
+
# # Same as above
|
181
|
+
# $: << '/tmp'
|
182
|
+
# lines = Dir.chdir('/tmp') do
|
183
|
+
# lines = LineCache::getlines ('myfile.rb')
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
def getline(filename, line_number, reload_on_change=true)
|
187
|
+
filename = unmap_file(filename)
|
188
|
+
filename, line_number = unmap_file_line(filename, line_number)
|
189
|
+
lines = getlines(filename, reload_on_change)
|
190
|
+
if lines and (1..lines.size) === line_number
|
191
|
+
return lines[line_number-1]
|
192
|
+
else
|
193
|
+
return nil
|
194
|
+
end
|
195
|
+
end
|
196
|
+
module_function :getline
|
197
|
+
|
198
|
+
# Read lines of +filename+ and cache the results. However +filename+ was
|
199
|
+
# previously cached use the results from the cache. Return nil
|
200
|
+
# if we can't get lines
|
201
|
+
def getlines(filename, reload_on_change=false)
|
202
|
+
filename = unmap_file(filename)
|
203
|
+
checkcache(filename) if reload_on_change
|
204
|
+
if @@file_cache.member?(filename)
|
205
|
+
return @@file_cache[filename].lines
|
206
|
+
else
|
207
|
+
update_cache(filename, true)
|
208
|
+
return @@file_cache[filename].lines if @@file_cache.member?(filename)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
module_function :getlines
|
212
|
+
|
213
|
+
# Return full filename path for filename
|
214
|
+
def path(filename)
|
215
|
+
filename = unmap_file(filename)
|
216
|
+
return nil unless @@file_cache.member?(filename)
|
217
|
+
@@file_cache[filename].path
|
218
|
+
end
|
219
|
+
module_function :path
|
220
|
+
|
221
|
+
def remap_file(from_file, to_file)
|
222
|
+
@@file2file_remap[to_file] = from_file
|
223
|
+
end
|
224
|
+
module_function :remap_file
|
225
|
+
|
226
|
+
def remap_file_lines(from_file, to_file, range, start)
|
227
|
+
range = (range..range) if range.is_a?(Fixnum)
|
228
|
+
to_file = from_file unless to_file
|
229
|
+
if @@file2file_remap_lines[to_file]
|
230
|
+
# FIXME: need to check for overwriting ranges: whether
|
231
|
+
# they intersect or one encompasses another.
|
232
|
+
@@file2file_remap_lines[to_file] << [from_file, range, start]
|
233
|
+
else
|
234
|
+
@@file2file_remap_lines[to_file] = [[from_file, range, start]]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
module_function :remap_file_lines
|
238
|
+
|
170
239
|
# Return SHA1 of filename.
|
171
240
|
def sha1(filename)
|
241
|
+
filename = unmap_file(filename)
|
172
242
|
return nil unless @@file_cache.member?(filename)
|
173
243
|
return @@file_cache[filename].sha1.hexdigest if
|
174
244
|
@@file_cache[filename].sha1
|
@@ -181,6 +251,14 @@ module LineCache
|
|
181
251
|
end
|
182
252
|
module_function :sha1
|
183
253
|
|
254
|
+
# Return the number of lines in filename
|
255
|
+
def size(filename)
|
256
|
+
filename = unmap_file(filename)
|
257
|
+
return nil unless @@file_cache.member?(filename)
|
258
|
+
@@file_cache[filename].lines.length
|
259
|
+
end
|
260
|
+
module_function :size
|
261
|
+
|
184
262
|
# Return File.stat in the cache for filename.
|
185
263
|
def stat(filename)
|
186
264
|
return nil unless @@file_cache.member?(filename)
|
@@ -188,18 +266,56 @@ module LineCache
|
|
188
266
|
end
|
189
267
|
module_function :stat
|
190
268
|
|
191
|
-
#
|
192
|
-
#
|
193
|
-
#
|
269
|
+
# Return an Array of breakpoints in filename.
|
270
|
+
# The list will contain an entry for each distinct line event call
|
271
|
+
# so it is possible (and possibly useful) for a line number appear more
|
272
|
+
# than once.
|
273
|
+
def trace_line_numbers(filename, reload_on_change=false)
|
274
|
+
fullname = cache(filename, reload_on_change)
|
275
|
+
return nil unless fullname
|
276
|
+
e = @@file_cache[filename]
|
277
|
+
unless e.line_numbers
|
278
|
+
e.line_numbers =
|
279
|
+
TraceLineNumbers.lnums_for_str_array(e.lines)
|
280
|
+
e.line_numbers = false unless e.line_numbers
|
281
|
+
end
|
282
|
+
e.line_numbers
|
283
|
+
end
|
284
|
+
module_function :trace_line_numbers
|
285
|
+
|
286
|
+
def unmap_file(file)
|
287
|
+
@@file2file_remap[file] ? @@file2file_remap[file] : file
|
288
|
+
end
|
289
|
+
module_function :unmap_file
|
290
|
+
|
291
|
+
def unmap_file_line(file, line)
|
292
|
+
if @@file2file_remap_lines[file]
|
293
|
+
@@file2file_remap_lines[file].each do |from_file, range, start|
|
294
|
+
if range === line
|
295
|
+
from_file = from_file || file
|
296
|
+
return [from_file, start+line-range.begin]
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
return [file, line]
|
301
|
+
end
|
302
|
+
module_function :unmap_file_line
|
303
|
+
|
304
|
+
# Update a cache entry. If something's
|
305
|
+
# wrong, return nil. Return true if the cache was updated and false
|
306
|
+
# if not. If use_script_lines is true, use that as the source for the
|
307
|
+
# lines of the file
|
194
308
|
def update_cache(filename, use_script_lines=false)
|
195
309
|
|
196
|
-
return
|
310
|
+
return nil unless filename
|
197
311
|
|
198
312
|
@@file_cache.delete(filename)
|
199
|
-
|
313
|
+
path = File.expand_path(filename)
|
200
314
|
|
201
315
|
if use_script_lines
|
202
|
-
[filename
|
316
|
+
list = [filename]
|
317
|
+
list << @@file2file_remap[path] if @@file2file_remap[path]
|
318
|
+
list.each do |name|
|
203
319
|
if !SCRIPT_LINES__[name].nil? && SCRIPT_LINES__[name] != true
|
204
320
|
begin
|
205
321
|
stat = File.stat(name)
|
@@ -207,39 +323,39 @@ module LineCache
|
|
207
323
|
stat = nil
|
208
324
|
end
|
209
325
|
lines = SCRIPT_LINES__[name]
|
210
|
-
@@file_cache[filename] = LineCacheInfo.new(stat, lines,
|
211
|
-
|
326
|
+
@@file_cache[filename] = LineCacheInfo.new(stat, nil, lines, path, nil)
|
327
|
+
@@file2file_remap[path] = filename
|
328
|
+
return true
|
212
329
|
end
|
213
330
|
end
|
214
331
|
end
|
215
332
|
|
216
|
-
if File.exist?(
|
217
|
-
stat = File.stat(
|
218
|
-
|
219
|
-
basename = File.basename(filename)
|
220
|
-
|
333
|
+
if File.exist?(path)
|
334
|
+
stat = File.stat(path)
|
335
|
+
elsif File.basename(filename) == filename
|
221
336
|
# try looking through the search path.
|
222
337
|
stat = nil
|
223
338
|
for dirname in $:
|
224
|
-
|
225
|
-
if File.exist?(
|
226
|
-
stat = File.stat(
|
339
|
+
path = File.join(dirname, filename)
|
340
|
+
if File.exist?(path)
|
341
|
+
stat = File.stat(path)
|
227
342
|
break
|
228
343
|
end
|
229
344
|
end
|
230
|
-
return
|
345
|
+
return false unless stat
|
231
346
|
end
|
232
347
|
begin
|
233
|
-
fp = File.open(
|
348
|
+
fp = File.open(path, 'r')
|
234
349
|
lines = fp.readlines()
|
235
350
|
fp.close()
|
236
351
|
rescue
|
237
|
-
## print '*** cannot open',
|
238
|
-
return
|
352
|
+
## print '*** cannot open', path, ':', msg
|
353
|
+
return nil
|
239
354
|
end
|
240
|
-
@@file_cache[filename] = LineCacheInfo.new(File.stat(
|
241
|
-
|
242
|
-
|
355
|
+
@@file_cache[filename] = LineCacheInfo.new(File.stat(path), nil, lines,
|
356
|
+
path, nil)
|
357
|
+
@@file2file_remap[path] = filename
|
358
|
+
return true
|
243
359
|
end
|
244
360
|
|
245
361
|
module_function :update_cache
|
@@ -247,24 +363,37 @@ module LineCache
|
|
247
363
|
end
|
248
364
|
|
249
365
|
# example usage
|
250
|
-
if __FILE__ == $0
|
251
|
-
($DEBUG and ['rcov'].include?(File.basename($0)))
|
366
|
+
if __FILE__ == $0
|
252
367
|
def yes_no(var)
|
253
368
|
return var ? "" : "not "
|
254
369
|
end
|
255
370
|
|
256
371
|
lines = LineCache::getlines(__FILE__)
|
257
|
-
puts "#{__FILE__} has #{
|
372
|
+
puts "#{__FILE__} has #{LineCache.size(__FILE__)} lines"
|
258
373
|
line = LineCache::getline(__FILE__, 6)
|
259
374
|
puts "The 6th line is\n#{line}"
|
375
|
+
line = LineCache::remap_file(__FILE__, 'another_name')
|
376
|
+
puts LineCache::getline('another_name', 7)
|
377
|
+
|
378
|
+
puts("Files cached: #{LineCache::cached_files.inspect}")
|
260
379
|
LineCache::update_cache(__FILE__)
|
261
380
|
LineCache::checkcache(__FILE__)
|
381
|
+
puts "#{__FILE__} has #{LineCache::size(__FILE__)} lines"
|
382
|
+
puts "#{__FILE__} trace line numbers:\n" +
|
383
|
+
"#{LineCache::trace_line_numbers(__FILE__).to_a.sort.inspect}"
|
262
384
|
puts("#{__FILE__} is %scached." %
|
263
385
|
yes_no(LineCache::cached?(__FILE__)))
|
264
|
-
LineCache::stat(__FILE__).inspect
|
386
|
+
puts LineCache::stat(__FILE__).inspect
|
387
|
+
puts "Full path: #{LineCache::path(__FILE__)}"
|
265
388
|
LineCache::checkcache # Check all files in the cache
|
266
389
|
LineCache::clear_file_cache
|
267
390
|
puts("#{__FILE__} is now %scached." %
|
268
391
|
yes_no(LineCache::cached?(__FILE__)))
|
269
|
-
|
392
|
+
digest = SCRIPT_LINES__.select{|k,v| k =~ /digest.rb$/}
|
393
|
+
puts digest.first[0] if digest
|
394
|
+
line = LineCache::getline(__FILE__, 7)
|
395
|
+
puts "The 7th line is\n#{line}"
|
396
|
+
LineCache::remap_file_lines(__FILE__, 'test2', (10..20), 6)
|
397
|
+
puts LineCache::getline('test2', 10)
|
398
|
+
puts "Remapped 10th line of test2 is\n#{line}"
|
270
399
|
end
|