rudebug 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rudebug.rb ADDED
@@ -0,0 +1,13 @@
1
+ begin
2
+ require 'rubygems'
3
+ rescue LoadError
4
+ # RubyGems not installed
5
+ end
6
+
7
+ DATA_PATH = File.expand_path(File.dirname(__FILE__))
8
+
9
+ require 'gtk2'
10
+ require 'libglade2'
11
+ require 'yaml'
12
+
13
+ require 'rudebug/main'
@@ -0,0 +1,203 @@
1
+ require 'rudebug/connection'
2
+
3
+ class RuDebug
4
+ attr_reader :pages
5
+
6
+ def initialize_connect()
7
+ @connect_dlg = @glade["connect-dialog"]
8
+ @connectdlg_entry = @glade["connectdlg-server-entry"]
9
+ @connectdlg_type = @glade["connectdlg-type-combobox"]
10
+ @connectdlg_btn = @glade["connectdlg-button"]
11
+
12
+ load_connect_servers()
13
+
14
+ @connect_btn = @glade["connect-button"]
15
+ @disconnect_btn = @glade["disconnect-button"]
16
+ @continue_btn = @glade["continue-button"]
17
+ @step_into_btn = @glade["step-into-button"]
18
+ @step_over_btn = @glade["step-over-button"]
19
+
20
+ @connect_menuitem = @glade["connect-menuitem"]
21
+ @disconnect_menuitem = @glade["disconnect-menuitem"]
22
+
23
+ @connection_status = @glade["status-label"]
24
+ @connect_dlg.transient_for = @window
25
+
26
+ @connect_page = @notebook.get_nth_page(0)
27
+ @not_connected = @connection_status.text
28
+ @connection = nil
29
+ end
30
+
31
+ def load_connect_servers()
32
+ default_servers = [
33
+ "localhost:8989",
34
+ "druby://localhost:42531"
35
+ ]
36
+
37
+ conf_servers = @config[:servers].to_ary rescue []
38
+ servers = (conf_servers + default_servers).uniq
39
+
40
+ servers.each do |server|
41
+ @connectdlg_entry.append_text(server)
42
+ end
43
+
44
+ @connectdlg_entry.active = 0
45
+ end
46
+
47
+ def save_connect_servers()
48
+ servers = []
49
+ @connectdlg_entry.model.each do |model, path, iter|
50
+ servers << iter[0]
51
+ end
52
+
53
+ @config[:servers] = servers
54
+ end
55
+
56
+ def on_connect_button_clicked()
57
+ @connect_dlg.show
58
+ @connectdlg_btn.grab_focus
59
+ end
60
+ alias :on_connect_activate :on_connect_button_clicked
61
+
62
+ def on_disconnect_button_clicked()
63
+ disconnect()
64
+ end
65
+ alias :on_disconnect_activate :on_disconnect_button_clicked
66
+
67
+ def on_connect_dialog_delete_event(dialog, event)
68
+ dialog.hide_on_delete
69
+ end
70
+
71
+ def on_connectdlg_server_entry_changed()
72
+ text = @connectdlg_entry.active_text
73
+ @connectdlg_type.active = text[/druby/i] ? 0 : 1
74
+ @connectdlg_btn.sensitive = text.size > 0
75
+ end
76
+
77
+ def on_connectdlg_server_entry_key_press_event(server_entry, event)
78
+ case event.keyval
79
+ when Gdk::Keyval::GDK_Return then
80
+ on_connectdlg_button_clicked()
81
+ else
82
+ return false
83
+ end
84
+
85
+ return true
86
+ end
87
+
88
+ def on_connectdlg_button_clicked()
89
+ text = @connectdlg_entry.active_text
90
+ @connect_dlg.hide
91
+
92
+ type_text = @connectdlg_type.active_text
93
+
94
+ con_type = case type_text
95
+ when "ruby-breakpoint" then Connection::Breakpoint
96
+ when "ruby-debug" then Connection::RubyDebug
97
+ end
98
+
99
+ unless text[/:\d/]
100
+ text += ":%d" % case type_text
101
+ when "ruby-breakpoint" then "wtf".to_i(36) # >:)
102
+ when "ruby-debug" then 8989
103
+ end
104
+ end
105
+
106
+ connect_to(text, con_type)
107
+ end
108
+
109
+ def connect_to(server, con_type)
110
+ @connection_status.text = @connect_text = "Connecting to %s..." % server
111
+
112
+ @connect_thread = Thread.new do
113
+ begin
114
+ @connection = con_type.new
115
+ @connection.on_session = method(:on_session)
116
+ @connection.on_disconnect = method(:on_disconnect)
117
+ @connection.connect(server)
118
+ rescue Exception => error
119
+ on_failure(error)
120
+ sleep 2
121
+ retry
122
+ end
123
+ end
124
+
125
+ update_connect_buttons()
126
+ end
127
+
128
+ def update_connect_buttons()
129
+ @connect_btn.sensitive = @connect_menuitem.sensitive = !@connection
130
+ @disconnect_btn.sensitive = @disconnect_menuitem.sensitive = @connection
131
+ end
132
+
133
+ def update_session_buttons()
134
+ has_page = current_page()
135
+ @continue_btn.sensitive = has_page
136
+
137
+ stepping = @connection.stepping_support?() if @connection
138
+ @step_into_btn.sensitive = @step_over_btn.sensitive = stepping && has_page
139
+ end
140
+
141
+ def disconnect()
142
+ connection = @connection
143
+ on_disconnect()
144
+
145
+ Thread.new do
146
+ connection.disconnect unless connection.nil?
147
+
148
+ unless @connect_thread.nil?
149
+ @connect_thread.kill
150
+ @connect_thread = nil
151
+ end
152
+ end
153
+ end
154
+
155
+ def current_page()
156
+ active_page = @notebook.get_nth_page(@notebook.page)
157
+ title = @notebook.get_tab_label_text(active_page)
158
+ @pages[title]
159
+ end
160
+
161
+ def on_session(session)
162
+ @notebook.remove_page(0) # Remove connect page
163
+ @notebook.show_tabs = true
164
+ @pages[session.title] = Page.new(self, session)
165
+ update_session_buttons()
166
+ end
167
+
168
+ def on_disconnect()
169
+ @connection = nil
170
+ update_connect_buttons()
171
+ update_session_buttons()
172
+
173
+ @notebook.show_tabs = false
174
+ # Remove pages (session and connection)
175
+ # TODO: Only remove connection
176
+ @notebook.remove_page(0) until @notebook.n_pages == 0
177
+
178
+ @connection_status.text = @not_connected
179
+ @notebook.append_page(@connect_page)
180
+ end
181
+
182
+ def on_failure(error)
183
+ puts error, error.backtrace
184
+
185
+ @connection_status.text = [
186
+ @connect_text,
187
+ "Failed: %s" % error,
188
+ "Will reconnect..."
189
+ ].join("\n\n")
190
+ end
191
+
192
+ def on_continue_button_clicked(btn)
193
+ btn.sensitive = false if current_page().continue().nil?
194
+ end
195
+
196
+ def on_step_into_button_clicked(btn)
197
+ btn.sensitive = false if current_page().step_into().nil?
198
+ end
199
+
200
+ def on_step_over_button_clicked(btn)
201
+ btn.sensitive = false if current_page().step_over().nil?
202
+ end
203
+ end
@@ -0,0 +1,3 @@
1
+ require 'rudebug/connection/base'
2
+ require 'rudebug/connection/breakpoint'
3
+ require 'rudebug/connection/ruby-debug'
@@ -0,0 +1,109 @@
1
+ class Connection
2
+ class RemoteException < Exception
3
+ end
4
+
5
+ class Base
6
+ attr_accessor :on_session, :on_disconnect
7
+
8
+ Code = <<-END_CODE
9
+ $rudebug = Module.new do
10
+ extend self
11
+
12
+ def inspect()
13
+ "$rudebug"
14
+ end
15
+ alias :name :inspect
16
+
17
+ attr_accessor :selected
18
+
19
+ const_set(:Refs, [])
20
+
21
+ def add_ref(ref)
22
+ $rudebug::Refs << ref
23
+ return ref
24
+ end
25
+
26
+ const_set(:Pairs, Class.new {
27
+ def self.[](*pairs)
28
+ new(*pairs)
29
+ end
30
+
31
+ def self.to_s()
32
+ "$rudebug::Pairs"
33
+ end
34
+
35
+ def initialize(*pairs)
36
+ @pairs = pairs
37
+ end
38
+
39
+ def inspect()
40
+ "{ %s }" % @pairs.map do |key, value|
41
+ [
42
+ key.is_a?(Numeric) ? key : key.to_sym.inspect,
43
+ $rudebug.inspect_obj(value)
44
+ ].join(" => ")
45
+ end.join(", ")
46
+ end
47
+
48
+ def each(&block)
49
+ @pairs.each(&block)
50
+ end
51
+
52
+ def size()
53
+ @pairs.size
54
+ end
55
+
56
+ include Enumerable
57
+ })
58
+
59
+ def to_pairs(items, &block)
60
+ $rudebug::Pairs[*items.map { |item| [item, block.call(item)] }]
61
+ end
62
+
63
+ def to_list(items)
64
+ to_pairs(0 .. items.size - 1) { |idx| items[idx] }
65
+ end
66
+
67
+ def inspect_obj(obj)
68
+ case obj
69
+ when UnboundMethod, Method then
70
+ inspect_method(obj)
71
+ else
72
+ #require 'pp'
73
+ #PP.pp(obj, "", 30).chomp
74
+ obj.inspect
75
+ end
76
+ end
77
+
78
+ def inspect_method(method)
79
+ re = /^\x23<(?:Unbound)?Method: ([^(]+)(?:\\(([^)]+)\\))?\x23(.+?)>$/
80
+ if md = re.match(method.inspect) then
81
+ klass, diff_origin, name = *md.captures
82
+ origin = diff_origin || klass
83
+ argc = method.arity
84
+ more = argc < 0
85
+ argc = -argc if more
86
+
87
+ old_name = "a"
88
+ args = (1 .. argc).map do |idx|
89
+ arg_name = old_name.dup
90
+ old_name.succ!
91
+ arg_name
92
+ end
93
+ args[-1][0, 0] = "*" if more
94
+
95
+ "%s\x23%s(%s)" % [origin, name, args.join(", ")]
96
+ else
97
+ method.inspect
98
+ end
99
+ end
100
+ end
101
+ END_CODE
102
+
103
+ def self.load_rudebug_code(session)
104
+ session.eval(Code)
105
+ end
106
+ end
107
+ end
108
+
109
+ eval(Connection::Base::Code)
@@ -0,0 +1,109 @@
1
+ require 'rudebug/connection/base'
2
+
3
+ class Connection::Breakpoint < Connection::Base
4
+ def stepping_support?()
5
+ false
6
+ end
7
+
8
+ class Session
9
+ attr_reader :title
10
+
11
+ def initialize(workspace, title = nil)
12
+ @workspace = workspace
13
+ @title = title
14
+ @done = false
15
+
16
+ Connection::Base.load_rudebug_code(self)
17
+ end
18
+
19
+ def eval(code)
20
+ @workspace.evaluate(nil, code).inspect
21
+ rescue Exception => e
22
+ raise(Connection::RemoteException, e.inspect)
23
+ end
24
+
25
+ def current_file()
26
+ Kernel.eval(eval("bp.file rescue @__bp_file"))
27
+ end
28
+
29
+ def current_line()
30
+ Kernel.eval(eval("bp.line rescue @__bp_line")).to_i
31
+ end
32
+
33
+ def source_code(file)
34
+ Kernel.eval(eval("File.read(%p)" % file)).chomp
35
+ end
36
+
37
+ def continue()
38
+ @done = true
39
+ return nil
40
+ end
41
+
42
+ def done?()
43
+ @done
44
+ end
45
+
46
+ # Safety
47
+ def step_into() end
48
+ def step_over() end
49
+ end
50
+
51
+ def connect(server)
52
+ require 'breakpoint'
53
+ require 'breakpoint/drb'
54
+ require 'drb'
55
+
56
+ DRb.start_service()
57
+
58
+ @service = DRbObject.new(nil, server)
59
+ @service.eval_handler = method(:eval_handler)
60
+ @service.handler = method(:breakpoint_handler)
61
+
62
+ @check_thread = Thread.new(
63
+ @service, @on_disconnect
64
+ ) do |service, on_disconnect|
65
+ loop do
66
+ begin
67
+ service.ping
68
+ rescue DRb::DRbConnError => error
69
+ on_disconnect.call()
70
+ disconnect()
71
+ break
72
+ end
73
+
74
+ sleep(0.5)
75
+ end
76
+ end
77
+ end
78
+
79
+ def disconnect()
80
+ begin
81
+ @service.handler = nil
82
+ @service.eval_handler = nil
83
+ rescue Exception
84
+ end
85
+
86
+ @check_thread.kill if @check_thread
87
+ @check_thread = nil
88
+
89
+ DRb.stop_service()
90
+ @service = nil
91
+ end
92
+
93
+ def title_filter(message)
94
+ message[/"([^"]+)"/, 1] || message #"
95
+ end
96
+
97
+ def breakpoint_handler(workspace, message)
98
+ title = title_filter(message)
99
+ session = Session.new(workspace, title)
100
+ @on_session.call(session)
101
+ sleep 0.1 until session.done?
102
+ end
103
+
104
+ def eval_handler(code)
105
+ result = eval(code, TOPLEVEL_BINDING)
106
+ Breakpoint.undumpify(result)
107
+ return result
108
+ end
109
+ end
@@ -0,0 +1,133 @@
1
+ require 'rudebug/connection/base'
2
+
3
+ class Connection::RubyDebug < Connection::Base
4
+ def stepping_support?()
5
+ true
6
+ end
7
+
8
+ class Session
9
+ attr_reader :title
10
+
11
+ def initialize(connection, location)
12
+ @connection = connection
13
+ @location = location
14
+ @title = location.join(":")
15
+ @done = false
16
+
17
+ Connection::Base.load_rudebug_code(self)
18
+ end
19
+
20
+ def execute(cmd)
21
+ result = @connection.execute(cmd)
22
+ result ? result.chomp : result
23
+ end
24
+
25
+ def eval(code)
26
+ cmd = %(
27
+ p begin;
28
+ [eval(%p, nil, *([
29
+ caller[0].split(":")[0],
30
+ caller[0].split(":")[1].to_i
31
+ ] rescue [])).inspect, nil];
32
+ rescue Exception;
33
+ [nil, $!.inspect];
34
+ end
35
+ ).strip.gsub(/\n\s*/, "")
36
+ result, error = *Kernel.eval(execute(cmd % [code, @location]))
37
+
38
+ raise(Connection::RemoteException, error) if error
39
+ return result
40
+ end
41
+
42
+ def current_file()
43
+ @location[0]
44
+ end
45
+
46
+ def current_line()
47
+ @location[1].to_i
48
+ end
49
+
50
+ def source_code(file)
51
+ Kernel.eval(eval("File.read(%p)" % file)).chomp
52
+ end
53
+
54
+ def generic_step(cmd)
55
+ result = execute(cmd)
56
+
57
+ if result then
58
+ @connection.location?(result)
59
+ else
60
+ @connection.on_disconnect.call()
61
+ @connection.disconnect()
62
+ return nil
63
+ end
64
+ end
65
+
66
+ def continue()
67
+ generic_step("cont")
68
+ end
69
+
70
+ def step_into()
71
+ generic_step("step")
72
+ end
73
+
74
+ def step_over()
75
+ generic_step("next")
76
+ end
77
+ end
78
+
79
+ def connect(server)
80
+ require 'ruby-debug'
81
+ require 'socket'
82
+ require 'thread'
83
+
84
+ host, port = */^(.+):(\d+)$/.match(server).captures
85
+ port = port.to_i
86
+
87
+ @socket = TCPSocket.new(host, port)
88
+ line = @socket.gets
89
+ location = location?(line)
90
+
91
+ result = @socket.gets.strip rescue nil
92
+ if !prompt?(result) then
93
+ raise "Not a ruby-debug server"
94
+ end
95
+
96
+ @mutex = Mutex.new
97
+
98
+ session = Session.new(self, location)
99
+ @on_session.call(session)
100
+ end
101
+
102
+ def prompt?(line)
103
+ line[/^PROMPT /] rescue nil
104
+ end
105
+
106
+ def location?(line)
107
+ if md = /^(.+?):(\d+): /.match(line) then
108
+ file, line = *md.captures
109
+ [file, line.to_i]
110
+ end
111
+ end
112
+
113
+ def execute(cmd)
114
+ @mutex.synchronize do
115
+ @socket.puts(cmd)
116
+ result = @socket.gets
117
+ prompt = @socket.gets
118
+
119
+ #p result
120
+ #p [:prompt, prompt]
121
+ return result
122
+ end
123
+ end
124
+
125
+ def disconnect()
126
+ begin
127
+ @socket.close
128
+ rescue Exception
129
+ end
130
+
131
+ @socket = @mutex = nil
132
+ end
133
+ end