pry-remote-enhanced 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.
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: []