debugger-linecache 1.0.0
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/AUTHORS +2 -0
- data/COPYING +340 -0
- data/ChangeLog +68 -0
- data/NEWS +32 -0
- data/OLD_README +50 -0
- data/README.md +5 -0
- data/Rakefile +169 -0
- data/VERSION +1 -0
- data/debugger-linecache.gemspec +19 -0
- data/ext/trace_nums/extconf.rb +26 -0
- data/ext/trace_nums/trace_nums.c +105 -0
- data/ext/trace_nums/trace_nums.h +111 -0
- data/lib/linecache19.rb +412 -0
- data/lib/tracelines19.rb +47 -0
- data/svn2cl_usermap +1 -0
- data/test/data/begin1.rb +3 -0
- data/test/data/begin2.rb +3 -0
- data/test/data/begin3.rb +6 -0
- data/test/data/block1.rb +7 -0
- data/test/data/block2.rb +4 -0
- data/test/data/case1.rb +6 -0
- data/test/data/case2.rb +5 -0
- data/test/data/case3.rb +5 -0
- data/test/data/case4.rb +4 -0
- data/test/data/case5.rb +10 -0
- data/test/data/class1.rb +5 -0
- data/test/data/comments1.rb +6 -0
- data/test/data/def1.rb +9 -0
- data/test/data/each1.rb +3 -0
- data/test/data/end.rb +3 -0
- data/test/data/for1.rb +4 -0
- data/test/data/if1.rb +4 -0
- data/test/data/if2.rb +4 -0
- data/test/data/if3.rb +9 -0
- data/test/data/if4.rb +14 -0
- data/test/data/if5.rb +7 -0
- data/test/data/if6.rb +4 -0
- data/test/data/if7.rb +8 -0
- data/test/data/match.rb +3 -0
- data/test/data/match3.rb +5 -0
- data/test/data/match3a.rb +6 -0
- data/test/data/not-lit.rb +6 -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 +2 -0
- data/test/test-linecache.rb +151 -0
- data/test/test-lnum.rb +36 -0
- data/test/test-tracelines.rb +41 -0
- metadata +118 -0
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# -*- Ruby -*-
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rake/testtask'
|
7
|
+
require 'rake/extensiontask'
|
8
|
+
|
9
|
+
Rake::ExtensionTask.new('trace_nums')
|
10
|
+
|
11
|
+
SO_NAME = "trace_nums.so"
|
12
|
+
|
13
|
+
# ------- Default Package ----------
|
14
|
+
PKG_VERSION = open(File.join(File.dirname(__FILE__), 'VERSION')) do
|
15
|
+
|f| f.readlines[0].chomp
|
16
|
+
end
|
17
|
+
PKG_NAME = 'linecache'
|
18
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
19
|
+
RUBY_FORGE_PROJECT = 'rocky-hacks'
|
20
|
+
RUBY_FORGE_USER = 'rockyb'
|
21
|
+
|
22
|
+
FILES = FileList[
|
23
|
+
'AUTHORS',
|
24
|
+
'COPYING',
|
25
|
+
'ChangeLog',
|
26
|
+
'NEWS',
|
27
|
+
'README',
|
28
|
+
'Rakefile',
|
29
|
+
'VERSION',
|
30
|
+
'ext/trace_nums.c',
|
31
|
+
'ext/trace_nums.h',
|
32
|
+
'ext/extconf.rb',
|
33
|
+
'lib/*.rb',
|
34
|
+
'test/*.rb',
|
35
|
+
'test/data/*.rb',
|
36
|
+
'test/short-file'
|
37
|
+
]
|
38
|
+
|
39
|
+
desc "Test everything."
|
40
|
+
test_task = task :test => :lib do
|
41
|
+
Rake::TestTask.new(:test) do |t|
|
42
|
+
t.pattern = 'test/test-*.rb'
|
43
|
+
t.verbose = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Create the core ruby-debug shared library extension"
|
48
|
+
task :lib do
|
49
|
+
Dir.chdir("ext") do
|
50
|
+
system("#{Gem.ruby} extconf.rb && make")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
desc "Test everything - same as test."
|
56
|
+
task :check => :test
|
57
|
+
|
58
|
+
desc "Create a GNU-style ChangeLog via svn2cl"
|
59
|
+
task :ChangeLog do
|
60
|
+
system("svn2cl --authors=svn2cl_usermap")
|
61
|
+
end
|
62
|
+
|
63
|
+
# Base GEM Specification
|
64
|
+
default_spec = Gem::Specification.new do |spec|
|
65
|
+
spec.name = "linecache"
|
66
|
+
|
67
|
+
spec.homepage = "http://rubyforge.org/projects/rocky-hacks/linecache"
|
68
|
+
spec.summary = "Read file with caching"
|
69
|
+
spec.description = <<-EOF
|
70
|
+
LineCache is a module for reading and caching lines. This may be useful for
|
71
|
+
example in a debugger where the same lines are shown many times.
|
72
|
+
EOF
|
73
|
+
|
74
|
+
spec.version = PKG_VERSION
|
75
|
+
|
76
|
+
spec.author = "R. Bernstein"
|
77
|
+
spec.email = "rockyb@rubyforge.net"
|
78
|
+
spec.platform = Gem::Platform::RUBY
|
79
|
+
spec.require_path = "lib"
|
80
|
+
spec.files = FILES.to_a
|
81
|
+
spec.extensions = ["ext/extconf.rb"]
|
82
|
+
|
83
|
+
spec.required_ruby_version = '>= 1.8.2'
|
84
|
+
spec.date = Time.now
|
85
|
+
spec.rubyforge_project = 'rocky-hacks'
|
86
|
+
|
87
|
+
# rdoc
|
88
|
+
spec.has_rdoc = true
|
89
|
+
spec.extra_rdoc_files = ['README', 'lib/linecache.rb', 'lib/tracelines.rb']
|
90
|
+
|
91
|
+
spec.test_files = FileList['test/*.rb']
|
92
|
+
end
|
93
|
+
|
94
|
+
# Rake task to build the default package
|
95
|
+
Rake::GemPackageTask.new(default_spec) do |pkg|
|
96
|
+
pkg.need_tar = true
|
97
|
+
end
|
98
|
+
|
99
|
+
task :default => [:test]
|
100
|
+
|
101
|
+
# Windows specification
|
102
|
+
win_spec = default_spec.clone
|
103
|
+
win_spec.extensions = []
|
104
|
+
## win_spec.platform = Gem::Platform::WIN32 # deprecated
|
105
|
+
win_spec.platform = 'mswin32'
|
106
|
+
win_spec.files += ["lib/#{SO_NAME}"]
|
107
|
+
|
108
|
+
desc "Create Windows Gem"
|
109
|
+
task :win32_gem do
|
110
|
+
# Copy the win32 extension the top level directory.
|
111
|
+
current_dir = File.expand_path(File.dirname(__FILE__))
|
112
|
+
source = File.join(current_dir, "ext", "win32", SO_NAME)
|
113
|
+
target = File.join(current_dir, "lib", SO_NAME)
|
114
|
+
cp(source, target)
|
115
|
+
|
116
|
+
# Create the gem, then move it to pkg.
|
117
|
+
Gem::Builder.new(win_spec).build
|
118
|
+
gem_file = "#{win_spec.name}-#{win_spec.version}-#{win_spec.platform}.gem"
|
119
|
+
mv(gem_file, "pkg/#{gem_file}")
|
120
|
+
|
121
|
+
# Remove win extension from top level directory.
|
122
|
+
rm(target)
|
123
|
+
end
|
124
|
+
|
125
|
+
desc "Publish linecache to RubyForge."
|
126
|
+
task :publish do
|
127
|
+
require 'rake/contrib/sshpublisher'
|
128
|
+
|
129
|
+
# Get ruby-debug path.
|
130
|
+
ruby_debug_path = File.expand_path(File.dirname(__FILE__))
|
131
|
+
|
132
|
+
publisher = Rake::SshDirPublisher.new("rockyb@rubyforge.org",
|
133
|
+
"/var/www/gforge-projects/rocky-hacks/linecache", ruby_debug_path)
|
134
|
+
end
|
135
|
+
|
136
|
+
desc "Remove built files"
|
137
|
+
task :clean => [:clobber_package, :clobber_rdoc] do
|
138
|
+
cd "ext" do
|
139
|
+
if File.exists?("Makefile")
|
140
|
+
sh "make clean"
|
141
|
+
rm "Makefile"
|
142
|
+
end
|
143
|
+
derived_files = Dir.glob(".o") + Dir.glob("*.so")
|
144
|
+
rm derived_files unless derived_files.empty?
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# --------- RDoc Documentation ------
|
149
|
+
desc "Generate rdoc documentation"
|
150
|
+
Rake::RDocTask.new("rdoc") do |rdoc|
|
151
|
+
rdoc.rdoc_dir = 'doc'
|
152
|
+
rdoc.title = "linecache"
|
153
|
+
# Show source inline with line numbers
|
154
|
+
rdoc.options << "--inline-source" << "--line-numbers"
|
155
|
+
# Make the readme file the start page for the generated html
|
156
|
+
rdoc.options << '--main' << 'README'
|
157
|
+
rdoc.rdoc_files.include('ext/**/*.c',
|
158
|
+
'lib/*.rb',
|
159
|
+
'README',
|
160
|
+
'COPYING')
|
161
|
+
end
|
162
|
+
|
163
|
+
desc "Publish the release files to RubyForge."
|
164
|
+
task :rubyforge_upload do
|
165
|
+
`rubyforge login`
|
166
|
+
release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} '#{PKG_NAME}-#{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.gem"
|
167
|
+
puts release_command
|
168
|
+
system(release_command)
|
169
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.1
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'rubygems' unless defined? Gem
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "debugger-linecache"
|
6
|
+
s.version = "1.0.0"
|
7
|
+
s.authors = ["R. Bernstein", "Mark Moseley", "Gabriel Horner"]
|
8
|
+
s.email = "gabriel.horner@gmail.com"
|
9
|
+
s.homepage = "http://github.com/cldwalker/debugger-linecache"
|
10
|
+
s.summary = %q{Read file with caching}
|
11
|
+
s.description = %q{Linecache is a module for reading and caching lines. This may be useful for
|
12
|
+
example in a debugger where the same lines are shown many times.
|
13
|
+
}
|
14
|
+
s.required_rubygems_version = ">= 1.3.6"
|
15
|
+
s.extra_rdoc_files = ["README.md"]
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.extensions << "ext/trace_nums/extconf.rb"
|
18
|
+
s.add_dependency("ruby_core_source", ">= 0.1.5")
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "mkmf"
|
2
|
+
require "ruby_core_source"
|
3
|
+
|
4
|
+
if RUBY_VERSION >= "1.8"
|
5
|
+
if RUBY_RELEASE_DATE < "2005-03-22"
|
6
|
+
STDERR.print("Ruby version is too old\n")
|
7
|
+
exit(1)
|
8
|
+
end
|
9
|
+
else
|
10
|
+
STDERR.print("Ruby version is too old\n")
|
11
|
+
exit(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
hdrs = proc {
|
15
|
+
have_header("vm_core.h") and have_header("version.h")
|
16
|
+
}
|
17
|
+
|
18
|
+
dir_config("ruby")
|
19
|
+
if !Ruby_core_source::create_makefile_with_core(hdrs, "trace_nums19")
|
20
|
+
STDERR.print("Makefile creation failed\n")
|
21
|
+
STDERR.print("*************************************************************\n\n")
|
22
|
+
STDERR.print(" NOTE: For Ruby 1.9 installation instructions, please see:\n\n")
|
23
|
+
STDERR.print(" http://wiki.github.com/mark-moseley/ruby-debug\n\n")
|
24
|
+
STDERR.print("*************************************************************\n\n")
|
25
|
+
exit(1)
|
26
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
/*
|
2
|
+
Ruby 1.9 version: (7/20/2009, Mark Moseley, mark@fast-software.com)
|
3
|
+
|
4
|
+
Now works with Ruby-1.9.1. Tested with p129 and p243.
|
5
|
+
|
6
|
+
This does not (and can not) function identically to the 1.8 version.
|
7
|
+
Line numbers are ordered differently. But ruby-debug doesn't seem
|
8
|
+
to mind the difference.
|
9
|
+
|
10
|
+
Also, 1.9 does not number lines with a "begin" statement.
|
11
|
+
|
12
|
+
All this 1.9 version does is compile into bytecode, disassemble it
|
13
|
+
using rb_iseq_disasm(), and parse the text output. This isn't a
|
14
|
+
great solution; it will break if the disassembly format changes.
|
15
|
+
Walking the iseq tree and decoding each instruction is pretty hairy,
|
16
|
+
though, so until I have a really compelling reason to go that route,
|
17
|
+
I'll leave it at this.
|
18
|
+
*/
|
19
|
+
#include <ruby.h>
|
20
|
+
#include <version.h>
|
21
|
+
#include <vm_core.h>
|
22
|
+
#include "trace_nums.h"
|
23
|
+
|
24
|
+
VALUE mTraceLineNumbers;
|
25
|
+
|
26
|
+
static inline const rb_data_type_t *
|
27
|
+
threadptr_data_type(void)
|
28
|
+
{
|
29
|
+
static const rb_data_type_t *thread_data_type;
|
30
|
+
if (!thread_data_type)
|
31
|
+
{
|
32
|
+
VALUE current_thread = rb_thread_current();
|
33
|
+
thread_data_type = RTYPEDDATA_TYPE(current_thread);
|
34
|
+
}
|
35
|
+
return thread_data_type;
|
36
|
+
}
|
37
|
+
|
38
|
+
#define ruby_threadptr_data_type *threadptr_data_type()
|
39
|
+
#define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()))
|
40
|
+
|
41
|
+
/* Return a list of trace hook line numbers for the string in Ruby source src*/
|
42
|
+
static VALUE
|
43
|
+
lnums_for_str(VALUE self, VALUE src)
|
44
|
+
{
|
45
|
+
VALUE result = rb_ary_new(); /* The returned array of line numbers. */
|
46
|
+
int len;
|
47
|
+
char *token;
|
48
|
+
char *disasm;
|
49
|
+
rb_thread_t *th;
|
50
|
+
VALUE iseqval;
|
51
|
+
VALUE disasm_val;
|
52
|
+
|
53
|
+
StringValue(src); /* Check that src is a string. */
|
54
|
+
th = GET_THREAD();
|
55
|
+
|
56
|
+
/* First compile to bytecode, using the method in eval_string_with_cref() in vm_eval.c */
|
57
|
+
th->parse_in_eval++;
|
58
|
+
th->mild_compile_error++;
|
59
|
+
iseqval = rb_iseq_compile(src, rb_str_new_cstr("(numbers_for_str)"), INT2FIX(1));
|
60
|
+
th->mild_compile_error--;
|
61
|
+
th->parse_in_eval--;
|
62
|
+
|
63
|
+
/* Disassemble the bytecode into text and parse into lines */
|
64
|
+
disasm_val = rb_iseq_disasm(iseqval);
|
65
|
+
if (disasm_val == Qnil)
|
66
|
+
return(result);
|
67
|
+
|
68
|
+
disasm = (char*)malloc(strlen(RSTRING_PTR(disasm_val))+1);
|
69
|
+
strcpy(disasm, RSTRING_PTR(disasm_val));
|
70
|
+
|
71
|
+
for (token = strtok(disasm, "\n"); token != NULL; token = strtok(NULL, "\n"))
|
72
|
+
{
|
73
|
+
/* look only for lines tracing RUBY_EVENT_LINE (1) */
|
74
|
+
if (strstr(token, "trace 1 ") == NULL)
|
75
|
+
continue;
|
76
|
+
len = strlen(token) - 1;
|
77
|
+
if (token[len] != ')')
|
78
|
+
continue;
|
79
|
+
len--;
|
80
|
+
if ((token[len] == '(') || (token[len] == ' '))
|
81
|
+
continue;
|
82
|
+
|
83
|
+
for (; len > 0; len--)
|
84
|
+
{
|
85
|
+
if (token[len] == ' ')
|
86
|
+
continue;
|
87
|
+
if ((token[len] >= '0') && (token[len] <= '9'))
|
88
|
+
continue;
|
89
|
+
if (token[len] == '(')
|
90
|
+
rb_ary_push(result, INT2NUM(atoi(token + len + 1))); /* trace found */
|
91
|
+
|
92
|
+
break;
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
free(disasm);
|
97
|
+
return result;
|
98
|
+
}
|
99
|
+
|
100
|
+
void Init_trace_nums19(void)
|
101
|
+
{
|
102
|
+
mTraceLineNumbers = rb_define_module("TraceLineNumbers");
|
103
|
+
rb_define_module_function(mTraceLineNumbers, "lnums_for_str",
|
104
|
+
lnums_for_str, 1);
|
105
|
+
}
|
@@ -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
|
+
const 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/lib/linecache19.rb
ADDED
@@ -0,0 +1,412 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# $Id$
|
3
|
+
#
|
4
|
+
# Copyright (C) 2007, 2008 Rocky Bernstein <rockyb@rubyforge.net>
|
5
|
+
#
|
6
|
+
# This program is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19
|
+
# 02110-1301 USA.
|
20
|
+
#
|
21
|
+
|
22
|
+
# Author:: Rocky Bernstein (mailto:rockyb@rubyforge.net)
|
23
|
+
#
|
24
|
+
# = linecache
|
25
|
+
# A module to read and cache lines of a Ruby program.
|
26
|
+
# == Version
|
27
|
+
# :include:VERSION
|
28
|
+
|
29
|
+
# == SYNOPSIS
|
30
|
+
#
|
31
|
+
# The LineCache module allows one to get any line from any file,
|
32
|
+
# caching lines of the file on first access to the file. Although the
|
33
|
+
# file may be any file, the common use is when the file is a Ruby
|
34
|
+
# script since parsing of the file is done to figure out where the
|
35
|
+
# statement boundaries are.
|
36
|
+
#
|
37
|
+
# The routines here may be is useful when a small random sets of lines
|
38
|
+
# are read from a single file, in particular in a debugger to show
|
39
|
+
# source lines.
|
40
|
+
#
|
41
|
+
#
|
42
|
+
# require 'linecache19'
|
43
|
+
# lines = LineCache::getlines('/tmp/myruby.rb')
|
44
|
+
# # The following lines have same effect as the above.
|
45
|
+
# $: << '/tmp'
|
46
|
+
# Dir.chdir('/tmp') {lines = LineCache::getlines('myruby.rb')
|
47
|
+
#
|
48
|
+
# line = LineCache::getline('/tmp/myruby.rb', 6)
|
49
|
+
# # Note lines[6] == line (if /tmp/myruby.rb has 6 lines)
|
50
|
+
#
|
51
|
+
# LineCache::clear_file_cache
|
52
|
+
# LineCache::clear_file_cache('/tmp/myruby.rb')
|
53
|
+
# LineCache::update_cache # Check for modifications of all cached files.
|
54
|
+
#
|
55
|
+
# Some parts of the interface is derived from the Python module of the
|
56
|
+
# same name.
|
57
|
+
#
|
58
|
+
|
59
|
+
# Defining SCRIPT_LINES__ causes Ruby to cache the lines of files
|
60
|
+
# it reads. The key the setting of __FILE__ at the time when Ruby does
|
61
|
+
# its read. LineCache keeps a separate copy of the lines elsewhere
|
62
|
+
# and never destroys SCRIPT_LINES__
|
63
|
+
SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
|
64
|
+
|
65
|
+
require 'digest/sha1'
|
66
|
+
require 'set'
|
67
|
+
|
68
|
+
begin require 'rubygems' rescue LoadError end
|
69
|
+
require 'tracelines19'
|
70
|
+
# require 'ruby-debug' ; Debugger.start
|
71
|
+
|
72
|
+
# = module LineCache
|
73
|
+
# A module to read and cache lines of a Ruby program.
|
74
|
+
module LineCache
|
75
|
+
LineCacheInfo = Struct.new(:stat, :line_numbers, :lines, :path, :sha1) unless
|
76
|
+
defined?(LineCacheInfo)
|
77
|
+
|
78
|
+
# The file cache. The key is a name as would be given by Ruby for
|
79
|
+
# __FILE__. The value is a LineCacheInfo object.
|
80
|
+
@@file_cache = {}
|
81
|
+
|
82
|
+
# Maps a string filename (a String) to a key in @@file_cache (a
|
83
|
+
# String).
|
84
|
+
#
|
85
|
+
# One important use of @@file2file_remap is mapping the a full path
|
86
|
+
# of a file into the name stored in @@file_cache or given by Ruby's
|
87
|
+
# __FILE__. Applications such as those that get input from users,
|
88
|
+
# may want canonicalize a file name before looking it up. This map
|
89
|
+
# gives a way to do that.
|
90
|
+
#
|
91
|
+
# Another related use is when a template system is used. Here we'll
|
92
|
+
# probably want to remap not only the file name but also line
|
93
|
+
# ranges. Will probably use this for that, but I'm not sure.
|
94
|
+
@@file2file_remap = {}
|
95
|
+
@@file2file_remap_lines = {}
|
96
|
+
|
97
|
+
# Clear the file cache entirely.
|
98
|
+
def clear_file_cache()
|
99
|
+
@@file_cache = {}
|
100
|
+
@@file2file_remap = {}
|
101
|
+
@@file2file_remap_lines = {}
|
102
|
+
end
|
103
|
+
module_function :clear_file_cache
|
104
|
+
|
105
|
+
# Return an array of cached file names
|
106
|
+
def cached_files()
|
107
|
+
@@file_cache.keys
|
108
|
+
end
|
109
|
+
module_function :cached_files
|
110
|
+
|
111
|
+
# Discard cache entries that are out of date. If +filename+ is +nil+
|
112
|
+
# all entries in the file cache +@@file_cache+ are checked.
|
113
|
+
# If we don't have stat information about a file, which can happen
|
114
|
+
# if the file was read from SCRIPT_LINES__ but no corresponding file
|
115
|
+
# is found, it will be kept. Return a list of invalidated filenames.
|
116
|
+
# nil is returned if a filename was given but not found cached.
|
117
|
+
def checkcache(filename=nil, use_script_lines=false)
|
118
|
+
|
119
|
+
if !filename
|
120
|
+
filenames = @@file_cache.keys()
|
121
|
+
elsif @@file_cache.member?(filename)
|
122
|
+
filenames = [filename]
|
123
|
+
else
|
124
|
+
return nil
|
125
|
+
end
|
126
|
+
|
127
|
+
result = []
|
128
|
+
for filename in filenames
|
129
|
+
next unless @@file_cache.member?(filename)
|
130
|
+
path = @@file_cache[filename].path
|
131
|
+
if File.exist?(path)
|
132
|
+
cache_info = @@file_cache[filename].stat
|
133
|
+
stat = File.stat(path)
|
134
|
+
if stat &&
|
135
|
+
(cache_info.size != stat.size or cache_info.mtime != stat.mtime)
|
136
|
+
result << filename
|
137
|
+
update_cache(filename, use_script_lines)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
return result
|
142
|
+
end
|
143
|
+
module_function :checkcache
|
144
|
+
|
145
|
+
# Cache filename if it's not already cached.
|
146
|
+
# Return the expanded filename for it in the cache
|
147
|
+
# or nil if we can't find the file.
|
148
|
+
def cache(filename, reload_on_change=false)
|
149
|
+
if @@file_cache.member?(filename)
|
150
|
+
checkcache(filename) if reload_on_change
|
151
|
+
else
|
152
|
+
update_cache(filename, true)
|
153
|
+
end
|
154
|
+
if @@file_cache.member?(filename)
|
155
|
+
@@file_cache[filename].path
|
156
|
+
else
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
module_function :cache
|
161
|
+
|
162
|
+
# Return true if filename is cached
|
163
|
+
def cached?(filename)
|
164
|
+
@@file_cache.member?(unmap_file(filename))
|
165
|
+
end
|
166
|
+
module_function :cached?
|
167
|
+
|
168
|
+
def cached_script?(filename)
|
169
|
+
# In 1.8.6, the SCRIPT_LINES__ filename key can be unqualified
|
170
|
+
# In 1.9.1 it's the fully qualified name
|
171
|
+
if RUBY_VERSION < "1.9"
|
172
|
+
SCRIPT_LINES__.member?(unmap_file(filename))
|
173
|
+
else
|
174
|
+
SCRIPT_LINES__.member?(File.expand_path(unmap_file(filename)))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
module_function :cached_script?
|
178
|
+
|
179
|
+
def empty?(filename)
|
180
|
+
filename=unmap_file(filename)
|
181
|
+
@@file_cache[filename].lines.empty?
|
182
|
+
end
|
183
|
+
module_function :empty?
|
184
|
+
|
185
|
+
# Get line +line_number+ from file named +filename+. Return nil if
|
186
|
+
# there was a problem. If a file named filename is not found, the
|
187
|
+
# function will look for it in the $: array.
|
188
|
+
#
|
189
|
+
# Examples:
|
190
|
+
#
|
191
|
+
# lines = LineCache::getline('/tmp/myfile.rb')
|
192
|
+
# # Same as above
|
193
|
+
# $: << '/tmp'
|
194
|
+
# lines = LineCache.getlines('myfile.rb')
|
195
|
+
#
|
196
|
+
def getline(filename, line_number, reload_on_change=true)
|
197
|
+
filename = unmap_file(filename)
|
198
|
+
filename, line_number = unmap_file_line(filename, line_number)
|
199
|
+
lines = getlines(filename, reload_on_change)
|
200
|
+
if lines and (1..lines.size) === line_number
|
201
|
+
return lines[line_number-1]
|
202
|
+
else
|
203
|
+
return nil
|
204
|
+
end
|
205
|
+
end
|
206
|
+
module_function :getline
|
207
|
+
|
208
|
+
# Read lines of +filename+ and cache the results. However +filename+ was
|
209
|
+
# previously cached use the results from the cache. Return nil
|
210
|
+
# if we can't get lines
|
211
|
+
def getlines(filename, reload_on_change=false)
|
212
|
+
filename = unmap_file(filename)
|
213
|
+
checkcache(filename) if reload_on_change
|
214
|
+
if @@file_cache.member?(filename)
|
215
|
+
return @@file_cache[filename].lines
|
216
|
+
else
|
217
|
+
update_cache(filename, true)
|
218
|
+
return @@file_cache[filename].lines if @@file_cache.member?(filename)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
module_function :getlines
|
222
|
+
|
223
|
+
# Return full filename path for filename
|
224
|
+
def path(filename)
|
225
|
+
filename = unmap_file(filename)
|
226
|
+
return nil unless @@file_cache.member?(filename)
|
227
|
+
@@file_cache[filename].path
|
228
|
+
end
|
229
|
+
module_function :path
|
230
|
+
|
231
|
+
def remap_file(from_file, to_file)
|
232
|
+
@@file2file_remap[to_file] = from_file
|
233
|
+
end
|
234
|
+
module_function :remap_file
|
235
|
+
|
236
|
+
def remap_file_lines(from_file, to_file, range, start)
|
237
|
+
range = (range..range) if range.is_a?(Fixnum)
|
238
|
+
to_file = from_file unless to_file
|
239
|
+
if @@file2file_remap_lines[to_file]
|
240
|
+
# FIXME: need to check for overwriting ranges: whether
|
241
|
+
# they intersect or one encompasses another.
|
242
|
+
@@file2file_remap_lines[to_file] << [from_file, range, start]
|
243
|
+
else
|
244
|
+
@@file2file_remap_lines[to_file] = [[from_file, range, start]]
|
245
|
+
end
|
246
|
+
end
|
247
|
+
module_function :remap_file_lines
|
248
|
+
|
249
|
+
# Return SHA1 of filename.
|
250
|
+
def sha1(filename)
|
251
|
+
filename = unmap_file(filename)
|
252
|
+
return nil unless @@file_cache.member?(filename)
|
253
|
+
return @@file_cache[filename].sha1.hexdigest if
|
254
|
+
@@file_cache[filename].sha1
|
255
|
+
sha1 = Digest::SHA1.new
|
256
|
+
@@file_cache[filename].lines.each do |line|
|
257
|
+
sha1 << line
|
258
|
+
end
|
259
|
+
@@file_cache[filename].sha1 = sha1
|
260
|
+
sha1.hexdigest
|
261
|
+
end
|
262
|
+
module_function :sha1
|
263
|
+
|
264
|
+
# Return the number of lines in filename
|
265
|
+
def size(filename)
|
266
|
+
filename = unmap_file(filename)
|
267
|
+
return nil unless @@file_cache.member?(filename)
|
268
|
+
@@file_cache[filename].lines.length
|
269
|
+
end
|
270
|
+
module_function :size
|
271
|
+
|
272
|
+
# Return File.stat in the cache for filename.
|
273
|
+
def stat(filename)
|
274
|
+
return nil unless @@file_cache.member?(filename)
|
275
|
+
@@file_cache[filename].stat
|
276
|
+
end
|
277
|
+
module_function :stat
|
278
|
+
|
279
|
+
# Return an Array of breakpoints in filename.
|
280
|
+
# The list will contain an entry for each distinct line event call
|
281
|
+
# so it is possible (and possibly useful) for a line number appear more
|
282
|
+
# than once.
|
283
|
+
def trace_line_numbers(filename, reload_on_change=false)
|
284
|
+
fullname = cache(filename, reload_on_change)
|
285
|
+
return nil unless fullname
|
286
|
+
e = @@file_cache[filename]
|
287
|
+
unless e.line_numbers
|
288
|
+
e.line_numbers =
|
289
|
+
TraceLineNumbers.lnums_for_str_array(e.lines)
|
290
|
+
e.line_numbers = false unless e.line_numbers
|
291
|
+
end
|
292
|
+
e.line_numbers
|
293
|
+
end
|
294
|
+
module_function :trace_line_numbers
|
295
|
+
|
296
|
+
def unmap_file(file)
|
297
|
+
@@file2file_remap[file] ? @@file2file_remap[file] : file
|
298
|
+
end
|
299
|
+
module_function :unmap_file
|
300
|
+
|
301
|
+
def unmap_file_line(file, line)
|
302
|
+
if @@file2file_remap_lines[file]
|
303
|
+
@@file2file_remap_lines[file].each do |from_file, range, start|
|
304
|
+
if range === line
|
305
|
+
from_file = from_file || file
|
306
|
+
return [from_file, start+line-range.begin]
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
return [file, line]
|
311
|
+
end
|
312
|
+
module_function :unmap_file_line
|
313
|
+
|
314
|
+
# Update a cache entry. If something's
|
315
|
+
# wrong, return nil. Return true if the cache was updated and false
|
316
|
+
# if not. If use_script_lines is true, use that as the source for the
|
317
|
+
# lines of the file
|
318
|
+
def update_cache(filename, use_script_lines=false)
|
319
|
+
|
320
|
+
return nil unless filename
|
321
|
+
|
322
|
+
@@file_cache.delete(filename)
|
323
|
+
path = File.expand_path(filename)
|
324
|
+
|
325
|
+
if use_script_lines
|
326
|
+
list = [filename]
|
327
|
+
list << @@file2file_remap[path] if @@file2file_remap[path]
|
328
|
+
list.each do |name|
|
329
|
+
if !SCRIPT_LINES__[name].nil? && SCRIPT_LINES__[name] != true
|
330
|
+
begin
|
331
|
+
stat = File.stat(name)
|
332
|
+
rescue
|
333
|
+
stat = nil
|
334
|
+
end
|
335
|
+
lines = SCRIPT_LINES__[name]
|
336
|
+
if "ruby19".respond_to?(:force_encoding)
|
337
|
+
lines.each{|l| l.force_encoding(Encoding.default_external) }
|
338
|
+
end
|
339
|
+
@@file_cache[filename] = LineCacheInfo.new(stat, nil, lines, path, nil)
|
340
|
+
@@file2file_remap[path] = filename
|
341
|
+
return true
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
if File.exist?(path)
|
347
|
+
stat = File.stat(path)
|
348
|
+
elsif File.basename(filename) == filename
|
349
|
+
# try looking through the search path.
|
350
|
+
stat = nil
|
351
|
+
for dirname in $:
|
352
|
+
path = File.join(dirname, filename)
|
353
|
+
if File.exist?(path)
|
354
|
+
stat = File.stat(path)
|
355
|
+
break
|
356
|
+
end
|
357
|
+
end
|
358
|
+
return false unless stat
|
359
|
+
end
|
360
|
+
begin
|
361
|
+
fp = File.open(path, 'r')
|
362
|
+
lines = fp.readlines()
|
363
|
+
fp.close()
|
364
|
+
rescue
|
365
|
+
## print '*** cannot open', path, ':', msg
|
366
|
+
return nil
|
367
|
+
end
|
368
|
+
@@file_cache[filename] = LineCacheInfo.new(File.stat(path), nil, lines,
|
369
|
+
path, nil)
|
370
|
+
@@file2file_remap[path] = filename
|
371
|
+
return true
|
372
|
+
end
|
373
|
+
|
374
|
+
module_function :update_cache
|
375
|
+
|
376
|
+
end
|
377
|
+
|
378
|
+
# example usage
|
379
|
+
if __FILE__ == $0
|
380
|
+
def yes_no(var)
|
381
|
+
return var ? "" : "not "
|
382
|
+
end
|
383
|
+
|
384
|
+
lines = LineCache::getlines(__FILE__)
|
385
|
+
puts "#{__FILE__} has #{LineCache.size(__FILE__)} lines"
|
386
|
+
line = LineCache::getline(__FILE__, 6)
|
387
|
+
puts "The 6th line is\n#{line}"
|
388
|
+
line = LineCache::remap_file(__FILE__, 'another_name')
|
389
|
+
puts LineCache::getline('another_name', 7)
|
390
|
+
|
391
|
+
puts("Files cached: #{LineCache::cached_files.inspect}")
|
392
|
+
LineCache::update_cache(__FILE__)
|
393
|
+
LineCache::checkcache(__FILE__)
|
394
|
+
puts "#{__FILE__} has #{LineCache::size(__FILE__)} lines"
|
395
|
+
puts "#{__FILE__} trace line numbers:\n" +
|
396
|
+
"#{LineCache::trace_line_numbers(__FILE__).to_a.sort.inspect}"
|
397
|
+
puts("#{__FILE__} is %scached." %
|
398
|
+
yes_no(LineCache::cached?(__FILE__)))
|
399
|
+
puts LineCache::stat(__FILE__).inspect
|
400
|
+
puts "Full path: #{LineCache::path(__FILE__)}"
|
401
|
+
LineCache::checkcache # Check all files in the cache
|
402
|
+
LineCache::clear_file_cache
|
403
|
+
puts("#{__FILE__} is now %scached." %
|
404
|
+
yes_no(LineCache::cached?(__FILE__)))
|
405
|
+
digest = SCRIPT_LINES__.select{|k,v| k =~ /digest.rb$/}
|
406
|
+
puts digest.first[0] if digest
|
407
|
+
line = LineCache::getline(__FILE__, 7)
|
408
|
+
puts "The 7th line is\n#{line}"
|
409
|
+
LineCache::remap_file_lines(__FILE__, 'test2', (10..20), 6)
|
410
|
+
puts LineCache::getline('test2', 10)
|
411
|
+
puts "Remapped 10th line of test2 is\n#{line}"
|
412
|
+
end
|