rudebug 0.3.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/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