pry-remote-enhanced 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/LICENSE +21 -0
  2. data/README.md +65 -0
  3. data/bin/pry-remote +4 -0
  4. data/lib/pry-remote.rb +380 -0
  5. metadata +81 -0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ pry-remote - Copyright (c) 2012 - Mon ouïe
2
+
3
+ This software is provided 'as-is', without any express or
4
+ implied warranty. In no event will the authors be held
5
+ liable for any damages arising from the use of this software.
6
+
7
+ Permission is granted to anyone to use this software for any purpose,
8
+ including commercial applications, and to alter it and redistribute
9
+ it freely, subject to the following restrictions:
10
+
11
+ 1. The origin of this software must not be misrepresented;
12
+ you must not claim that you wrote the original software.
13
+ If you use this software in a product, an acknowledgment
14
+ in the product documentation would be appreciated but
15
+ is not required.
16
+
17
+ 2. Altered source versions must be plainly marked as such,
18
+ and must not be misrepresented as being the original software.
19
+
20
+ 3. This notice may not be removed or altered from any
21
+ source distribution.
@@ -0,0 +1,65 @@
1
+ # What is it?
2
+
3
+ A way to start Pry remotely and to connect to it using DRb. This allows to
4
+ access the state of the running program from anywhere.
5
+
6
+ This repository is a fork of pry-remote with aims to:
7
+
8
+ * Resolve existing issues in pry-remote
9
+ * Refactor architecture to use more idiomatic Ruby
10
+ * Add a testing suite
11
+ * Improve support for Sidekiq and Foreman
12
+
13
+ # Installation
14
+
15
+ gem install pry-remote-enhanced
16
+
17
+ # Usage
18
+
19
+ Here's a program starting pry-remote:
20
+
21
+ require 'pry-remote'
22
+
23
+ class Foo
24
+ def initialize(x, y)
25
+ binding.remote_pry
26
+ end
27
+ end
28
+
29
+ Foo.new 10, 20
30
+
31
+ Running it will prompt you with a message telling you Pry is waiting for a
32
+ program to connect itself to it:
33
+
34
+ [pry-remote] Waiting for client on drb://localhost:9876
35
+
36
+ You can then connect yourself using ``pry-remote``:
37
+
38
+ $ pry-remote
39
+ From: example.rb @ line 7 in Foo#initialize:
40
+ 2:
41
+ 3: require 'pry-remote'
42
+ 4:
43
+ 5: class Foo
44
+ 6: def initialize(x, y)
45
+ => 7: binding.remote_pry
46
+ 8: end
47
+ 9: end
48
+ 10:
49
+ 11: Foo.new 10, 20
50
+ pry(#<Foo:0x00000000d9b5e8>):1> self
51
+ => #<Foo:0x1efb3b0>
52
+ pry(#<Foo:0x00000001efb3b0>):2> ls -l
53
+ Local variables: [
54
+ [0] :_,
55
+ [1] :_dir_,
56
+ [2] :_file_,
57
+ [3] :_ex_,
58
+ [4] :_pry_,
59
+ [5] :_out_,
60
+ [6] :_in_,
61
+ [7] :x,
62
+ [8] :y
63
+ ]
64
+ pry(#<Foo:0x00000001efb3b0>):3> ^D
65
+
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pry-remote'
4
+ PryRemote::CLI.new.run
@@ -0,0 +1,380 @@
1
+ require 'pry'
2
+ require 'slop'
3
+ require 'drb'
4
+ require 'readline'
5
+ require 'open3'
6
+
7
+ module PryRemote
8
+ DefaultHost = "127.0.0.1"
9
+ DefaultPort = 9876
10
+
11
+ # A class to represent an input object created from DRb. This is used because
12
+ # Pry checks for arity to know if a prompt should be passed to the object.
13
+ #
14
+ # @attr [#readline] input Object to proxy
15
+ InputProxy = Struct.new :input do
16
+ # Reads a line from the input
17
+ def readline(prompt)
18
+ case readline_arity
19
+ when 1 then input.readline(prompt)
20
+ else input.readline
21
+ end
22
+ end
23
+
24
+ def completion_proc=(val)
25
+ input.completion_proc = val
26
+ end
27
+
28
+ def readline_arity
29
+ input.method_missing(:method, :readline).arity
30
+ rescue NameError
31
+ 0
32
+ end
33
+ end
34
+
35
+ # Class used to wrap inputs so that they can be sent through DRb.
36
+ #
37
+ # This is to ensure the input is used locally and not reconstructed on the
38
+ # server by DRb.
39
+ class IOUndumpedProxy
40
+ include DRb::DRbUndumped
41
+
42
+ def initialize(obj)
43
+ @obj = obj
44
+ end
45
+
46
+ def completion_proc=(val)
47
+ if @obj.respond_to? :completion_proc=
48
+ @obj.completion_proc = proc { |*args, &block| val.call(*args, &block) }
49
+ end
50
+ end
51
+
52
+ def completion_proc
53
+ @obj.completion_proc if @obj.respond_to? :completion_proc
54
+ end
55
+
56
+ def readline(prompt)
57
+ if Readline == @obj
58
+ @obj.readline(prompt, true)
59
+ elsif @obj.method(:readline).arity == 1
60
+ @obj.readline(prompt)
61
+ else
62
+ $stdout.print prompt
63
+ @obj.readline
64
+ end
65
+ end
66
+
67
+ def puts(*lines)
68
+ @obj.puts(*lines)
69
+ end
70
+
71
+ def print(*objs)
72
+ @obj.print(*objs)
73
+ end
74
+
75
+ def printf(*args)
76
+ @obj.printf(*args)
77
+ end
78
+
79
+ def write(data)
80
+ @obj.write data
81
+ end
82
+
83
+ def <<(data)
84
+ @obj << data
85
+ self
86
+ end
87
+
88
+ # Some versions of Pry expect $stdout or its output objects to respond to
89
+ # this message.
90
+ def tty?
91
+ false
92
+ end
93
+ end
94
+
95
+ # Ensure that system (shell command) output is redirected for remote session.
96
+ System = proc do |output, cmd, _|
97
+ status = nil
98
+ Open3.popen3 cmd do |stdin, stdout, stderr, wait_thr|
99
+ stdin.close # Send EOF to the process
100
+
101
+ until stdout.eof? and stderr.eof?
102
+ if res = IO.select([stdout, stderr])
103
+ res[0].each do |io|
104
+ next if io.eof?
105
+ output.write io.read_nonblock(1024)
106
+ end
107
+ end
108
+ end
109
+
110
+ status = wait_thr.value
111
+ end
112
+
113
+ unless status.success?
114
+ output.puts "Error while executing command: #{cmd}"
115
+ end
116
+ end
117
+
118
+ ClientEditor = proc do |initial_content, line|
119
+ # Hack to use Pry::Editor
120
+ Pry::Editor.new(Pry.new).edit_tempfile_with_content(initial_content, line)
121
+ end
122
+
123
+ # A client is used to retrieve information from the client program.
124
+ Client = Struct.new(:input, :output, :thread, :stdout, :stderr,
125
+ :editor) do
126
+ # Waits until both an input and output are set
127
+ def wait
128
+ sleep 0.01 until input and output and thread
129
+ end
130
+
131
+ # Tells the client the session is terminated
132
+ def kill
133
+ thread.run
134
+ end
135
+
136
+ # @return [InputProxy] Proxy for the input
137
+ def input_proxy
138
+ InputProxy.new input
139
+ end
140
+ end
141
+
142
+ class Server
143
+ def self.run(object, host = DefaultHost, port = DefaultPort, options = {})
144
+ new(object, host, port, options).run
145
+ end
146
+
147
+ def initialize(object, host = DefaultHost, port = DefaultPort, options = {})
148
+ @host = host
149
+ @port = port
150
+
151
+ @object = object
152
+ @options = options
153
+
154
+ @client = PryRemote::Client.new
155
+ DRb.start_service uri, @client
156
+ end
157
+
158
+ # Code that has to be called for Pry-remote to work properly
159
+ def setup
160
+ @hooks = Pry::Hooks.new
161
+
162
+ @hooks.add_hook :before_eval, :pry_remote_capture do
163
+ capture_output
164
+ end
165
+
166
+ @hooks.add_hook :after_eval, :pry_remote_uncapture do
167
+ uncapture_output
168
+ end
169
+
170
+ # Before Pry starts, save the pager config.
171
+ # We want to disable this because the pager won't do anything useful in
172
+ # this case (it will run on the server).
173
+ Pry.config.pager, @old_pager = false, Pry.config.pager
174
+
175
+ # As above, but for system config
176
+ Pry.config.system, @old_system = PryRemote::System, Pry.config.system
177
+
178
+ Pry.config.editor, @old_editor = editor_proc, Pry.config.editor
179
+ end
180
+
181
+ # Code that has to be called after setup to return to the initial state
182
+ def teardown
183
+ # Reset config
184
+ Pry.config.editor = @old_editor
185
+ Pry.config.pager = @old_pager
186
+ Pry.config.system = @old_system
187
+
188
+ puts "[pry-remote] Remote session terminated"
189
+
190
+ begin
191
+ @client.kill
192
+ rescue DRb::DRbConnError
193
+ puts "[pry-remote] Continuing to stop service"
194
+ ensure
195
+ puts "[pry-remote] Ensure stop service"
196
+ DRb.stop_service
197
+ end
198
+ end
199
+
200
+ # Captures $stdout and $stderr if so requested by the client.
201
+ def capture_output
202
+ @old_stdout, $stdout = if @client.stdout
203
+ [$stdout, @client.stdout]
204
+ else
205
+ [$stdout, $stdout]
206
+ end
207
+
208
+ @old_stderr, $stderr = if @client.stderr
209
+ [$stderr, @client.stderr]
210
+ else
211
+ [$stderr, $stderr]
212
+ end
213
+ end
214
+
215
+ # Resets $stdout and $stderr to their previous values.
216
+ def uncapture_output
217
+ $stdout = @old_stdout
218
+ $stderr = @old_stderr
219
+ end
220
+
221
+ def editor_proc
222
+ proc do |file, line|
223
+ File.write(file, @client.editor.call(File.read(file), line))
224
+ end
225
+ end
226
+
227
+ # Actually runs pry-remote
228
+ def run
229
+ puts "[pry-remote] Waiting for client on #{uri}"
230
+ @client.wait
231
+
232
+ puts "[pry-remote] Client received, starting remote session"
233
+ setup
234
+
235
+ Pry.start(@object, @options.merge(:input => client.input_proxy,
236
+ :output => client.output,
237
+ :hooks => @hooks))
238
+ ensure
239
+ teardown
240
+ end
241
+
242
+ # @return Object to enter into
243
+ attr_reader :object
244
+
245
+ # @return [PryServer::Client] Client connecting to the pry-remote server
246
+ attr_reader :client
247
+
248
+ # @return [String] Host of the server
249
+ attr_reader :host
250
+
251
+ # @return [Integer] Port of the server
252
+ attr_reader :port
253
+
254
+ # @return [String] URI for DRb
255
+ def uri
256
+ "druby://#{host}:#{port}"
257
+ end
258
+ end
259
+
260
+ # Parses arguments and allows to start the client.
261
+ class CLI
262
+ def initialize(args = ARGV)
263
+ params = Slop.parse args, :help => true do
264
+ banner "#$PROGRAM_NAME [OPTIONS]"
265
+
266
+ on :s, :server=, "Host of the server (#{DefaultHost})", :argument => :optional,
267
+ :default => DefaultHost
268
+ on :p, :port=, "Port of the server (#{DefaultPort})", :argument => :optional,
269
+ :as => Integer, :default => DefaultPort
270
+ on :w, :wait, "Wait for the pry server to come up",
271
+ :default => false
272
+ on :r, :persist, "Persist the client to wait for the pry server to come up each time",
273
+ :default => false
274
+ on :c, :capture, "Captures $stdout and $stderr from the server (true)",
275
+ :default => true
276
+ on :f, "Disables loading of .pryrc and its plugins, requires, and command history "
277
+ end
278
+
279
+ exit if params.help?
280
+
281
+ @host = params[:server]
282
+ @port = params[:port]
283
+
284
+ @wait = params[:wait]
285
+ @persist = params[:persist]
286
+ @capture = params[:capture]
287
+
288
+ Pry.initial_session_setup unless params[:f]
289
+ end
290
+
291
+ # @return [String] Host of the server
292
+ attr_reader :host
293
+
294
+ # @return [Integer] Port of the server
295
+ attr_reader :port
296
+
297
+ # @return [String] URI for DRb
298
+ def uri
299
+ "druby://#{host}:#{port}"
300
+ end
301
+
302
+ attr_reader :wait
303
+ attr_reader :persist
304
+ attr_reader :capture
305
+ alias wait? wait
306
+ alias persist? persist
307
+ alias capture? capture
308
+
309
+ def run
310
+ while true
311
+ connect
312
+ break unless persist?
313
+ end
314
+ end
315
+
316
+ # Connects to the server
317
+ #
318
+ # @param [IO] input Object holding input for pry-remote
319
+ # @param [IO] output Object pry-debug will send its output to
320
+ def connect(input = Pry.config.input, output = Pry.config.output)
321
+ local_ip = UDPSocket.open {|s| s.connect(@host, 1); s.addr.last}
322
+ DRb.start_service "druby://#{local_ip}:0"
323
+ client = DRbObject.new(nil, uri)
324
+
325
+ cleanup(client)
326
+
327
+ input = IOUndumpedProxy.new(input)
328
+ output = IOUndumpedProxy.new(output)
329
+
330
+ begin
331
+ client.input = input
332
+ client.output = output
333
+ rescue DRb::DRbConnError => ex
334
+ if wait? || persist?
335
+ sleep 1
336
+ retry
337
+ else
338
+ raise ex
339
+ end
340
+ end
341
+
342
+ if capture?
343
+ client.stdout = $stdout
344
+ client.stderr = $stderr
345
+ end
346
+
347
+ client.editor = ClientEditor
348
+
349
+ client.thread = Thread.current
350
+
351
+ sleep
352
+ DRb.stop_service
353
+ end
354
+
355
+ # Clean up the client
356
+ def cleanup(client)
357
+ begin
358
+ # The method we are calling here doesn't matter.
359
+ # This is a hack to close the connection of DRb.
360
+ client.cleanup
361
+ rescue DRb::DRbConnError, NoMethodError
362
+ end
363
+ end
364
+ end
365
+ end
366
+
367
+ class Object
368
+ # Starts a remote Pry session
369
+ #
370
+ # @param [String] host Host of the server
371
+ # @param [Integer] port Port of the server
372
+ # @param [Hash] options Options to be passed to Pry.start
373
+ def remote_pry(host = PryRemote::DefaultHost, port = PryRemote::DefaultPort, options = {})
374
+ PryRemote::Server.new(self, host, port, options).run
375
+ end
376
+
377
+ # a handy alias as many people may think the method is named after the gem
378
+ # (pry-remote)
379
+ alias pry_remote remote_pry
380
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pry-remote-enhanced
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joseph Jaber
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-09-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: slop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: pry
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.9'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.9'
46
+ description: A modern fork of pry-remote
47
+ email: mail@josephjaber.com
48
+ executables:
49
+ - pry-remote
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - lib/pry-remote.rb
54
+ - README.md
55
+ - LICENSE
56
+ - bin/pry-remote
57
+ homepage: http://github.com/josephjaber/pry-remote
58
+ licenses: []
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 1.8.23.2
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: A modern fork of pry-remote
81
+ test_files: []