pry-remote-em 1.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'uri'
4
+ require 'readline'
5
+ require 'highline'
6
+ require 'pry-remote-em/client'
7
+ require 'optparse'
8
+
9
+ options = {}
10
+ OptionParser.new do |opts|
11
+ opts.on('-c', '--connect NAME', 'connect to the first pry remote em server matching NAME') do |name|
12
+ options[:connect] = name
13
+ end
14
+ opts.on('-p', '--proxy NAME', 'proxy through the broker to the first pry remote em server matching NAME') do |name|
15
+ options[:proxy] = name
16
+ end
17
+ opts.on('-P', '--proxy-by-default', 'show servers table with proxy mode enabled by default (ignored on -c or -p)') do |name|
18
+ options[:proxy_by_default] = true
19
+ end
20
+
21
+ opts.on('--fh HOST', '--filter-host HOST', 'only show servers listening at the given address (regexp)') do |host|
22
+ if host =~ /^pryems?:\/\//
23
+ ARGV.push(host)
24
+ else
25
+ options[:filter_host] = Regexp.new(host)
26
+ end
27
+ end
28
+ opts.on('--fn NAME', '--filter-name NAME', 'only show servers with a matching name (regexp)') do |name|
29
+ if name =~ /^pryems?:\/\//
30
+ ARGV.push(name)
31
+ else
32
+ options[:filter_name] = Regexp.new(name)
33
+ end
34
+ end
35
+ opts.on('--[no-]fs', '--[no-]filter-ssl', 'show only servers that support ssl') do |ssl|
36
+ options[:filter_ssl] = ssl
37
+ end
38
+
39
+ opts.on('--sh', '--sort-host', 'sort by host') { options[:sort] = :host }
40
+ opts.on('--sn', '--sort-name', 'sort by server name') { |name| options[:sort] = :name }
41
+ opts.on('--sp', '--sort-port', 'sort by port') { options[:sort] = :port }
42
+ opts.on('--ss', '--sort-ssl', 'sort by ssl support') { options[:sort] = :ssl }
43
+
44
+ opts.on('-d', '--details KEY', "show value from server's details option by given key instead of url in table, use @ to show all details") do |key|
45
+ options[:show_details] = key
46
+ end
47
+
48
+ opts.on('-m', '--metrics KEY', "show value from server's metrics by given key in a third column, use @ to show all metrics, default - errors (if any)") do |key|
49
+ options[:show_metrics] = key
50
+ end
51
+
52
+ opts.on('-i', '--ignore-localhost', 'filter out localhost urls from list') do
53
+ options[:ignore_localhost] = true
54
+ end
55
+
56
+ opts.parse!(ARGV)
57
+ end
58
+
59
+ uri = if ARGV[0].nil? || ARGV[0].empty?
60
+ host = ENV['PRYEMBROKER'].nil? || ENV['PRYEMBROKER'].empty? ? PryRemoteEm::DEFAULT_BROKER_HOST : ENV['PRYEMBROKER']
61
+ port = ENV['PRYEMBROKERPORT'].nil? || ENV['PRYEMBROKERPORT'].empty? ? PryRemoteEm::DEFAULT_BROKER_PORT : ENV['PRYEMBROKERPORT']
62
+ "pryem://#{host}:#{port}"
63
+ else
64
+ ARGV[0]
65
+ end
66
+ uri = URI.parse(uri)
67
+ unless %w(pryem pryems).include?(uri.scheme)
68
+ abort "only pryem URIs are currently supported\n usage: pryem[s]://#{PryRemoteEm::DEFAULT_BROKER_HOST}:#{PryRemoteEm::DEFAULT_BROKER_PORT}"
69
+ end
70
+ uri.port = PryRemoteEm::DEFAULT_BROKER_PORT unless uri.port
71
+
72
+ tried = 0
73
+ auth_proc = proc do
74
+ tried += 1
75
+ user = uri.user || ($stdin.tty? ? Readline.readline('user: ') : raise('username is require for authentication'))
76
+ pass = if !uri.password.nil? && tried <= 1
77
+ uri.password
78
+ elsif $stdin.tty?
79
+ HighLine.new.ask("#{user}'s password: ") { |q| q.echo = '*' }
80
+ else
81
+ raise 'password is required to authenticate'
82
+ end
83
+ [user, pass]
84
+ end
85
+
86
+ EM.run do
87
+ client_options = options.merge(auth: auth_proc, tls: uri.scheme == 'pryems')
88
+ PryRemoteEm::Client.start(uri.host, uri.port, client_options) { EM.stop }
89
+ end
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pry-remote-em/broker'
4
+ require 'optparse'
5
+
6
+ options = { tls: false }
7
+ OptionParser.new do |opts|
8
+ opts.on('-h', '--host HOST', 'host to bind broker server (same as PRYEMBROKER variable, default: "127.0.0.1")') do |host|
9
+ options[:host] = host
10
+ end
11
+
12
+ opts.on('-p', '--port PORT', 'port to bind broker server (same as PRYEMBROKERPORT variable, default: 6462)') do |port|
13
+ options[:port] = port
14
+ end
15
+
16
+ opts.on('-s', '--tls', 'use TLS for broker (default: false)') do
17
+ options[:tls] = true
18
+ end
19
+
20
+ opts.parse!(ARGV)
21
+ end
22
+
23
+
24
+ EM.run do
25
+ trap(:INT) { EM.stop }
26
+
27
+ PryRemoteEm::Broker.run options[:host], options[:port], tls: options[:tls], raise_if_port_in_use: true
28
+ end
@@ -0,0 +1,74 @@
1
+ begin
2
+ require 'openssl'
3
+ rescue LoadError
4
+ warn 'OpenSSL support is not available'
5
+ end
6
+ require 'socket'
7
+ require 'fiber'
8
+ require 'uri'
9
+ require 'eventmachine'
10
+ require 'pry-remote-em/version'
11
+ require 'pry-remote-em/proto'
12
+ require 'pry-remote-em/server'
13
+
14
+ module PryRemoteEm
15
+ DEFAULT_SERVER_HOST = '127.0.0.1'
16
+ DEFAULT_SERVER_PORT = 6463
17
+ DEFAULT_BROKER_HOST = '127.0.0.1'
18
+ DEFAULT_BROKER_PORT = 6462
19
+
20
+ NEGOTIATION_TIMEOUT = 15
21
+ HEARTBEAT_SEND_INTERVAL = 15
22
+ HEARTBEAT_CHECK_INTERVAL = 20
23
+ RECONNECT_TO_BROKER_TIMEOUT = 3
24
+
25
+ MAXIMUM_ERRORS_IN_SANDBOX = 100
26
+ end
27
+
28
+
29
+ class Object
30
+ def remote_pry_em(host = nil, port = nil, options = {}, &block)
31
+ host, options = nil, host if host.kind_of?(Hash) # Support for options hash as first argument instead of third
32
+
33
+ options = { target: self, host: host, port: port }.merge(options)
34
+ PryRemoteEm::Server.run(options, &block)
35
+ end
36
+
37
+ alias pry_remote_em remote_pry_em # source of common confusing
38
+ end
39
+
40
+
41
+ unless defined?(EventMachine.popen3)
42
+ module EventMachine
43
+ # @see http://eventmachine.rubyforge.org/EventMachine.html#M000491
44
+ # @see https://gist.github.com/535644/4d5b645b96764e07ccb53539529bea9270741e1a
45
+ def self.popen3(cmd, handler=nil, *args)
46
+ klass = klass_from_handler(Connection, handler, *args)
47
+ w = Shellwords::shellwords(cmd)
48
+ w.unshift(w.first) if w.first
49
+
50
+ new_stderr = $stderr.dup
51
+ rd, wr = IO::pipe
52
+
53
+ $stderr.reopen wr
54
+ s = invoke_popen(w)
55
+ $stderr.reopen new_stderr
56
+
57
+ klass.new(s, *args).tap do |c|
58
+ EM.attach(rd, Popen3StderrHandler, c)
59
+ @conns[s] = c
60
+ yield(c) if block_given?
61
+ end
62
+ end
63
+
64
+ class Popen3StderrHandler < EventMachine::Connection
65
+ def initialize(connection)
66
+ @connection = connection
67
+ end
68
+
69
+ def receive_data(data)
70
+ @connection.receive_stderr(data)
71
+ end
72
+ end # class::Popen3StderrHandler
73
+ end # module::EventMachine
74
+ end # defined?(EventMachine.popen3)
@@ -0,0 +1,229 @@
1
+ require 'logger'
2
+ require 'socket'
3
+ require 'pry-remote-em'
4
+ require 'pry-remote-em/client/broker'
5
+ require 'pry-remote-em/client/proxy'
6
+
7
+ module PryRemoteEm
8
+ module Broker
9
+ class << self
10
+ attr_reader :listening, :host, :port
11
+ alias :listening? :listening
12
+
13
+ def run(host = nil, port = nil, opts = {})
14
+ host ||= ENV['PRYEMBROKER'].nil? || ENV['PRYEMBROKER'].empty? ? DEFAULT_BROKER_HOST : ENV['PRYEMBROKER']
15
+ port ||= ENV['PRYEMBROKERPORT'].nil? || ENV['PRYEMBROKERPORT'].empty? ? DEFAULT_BROKER_PORT : ENV['PRYEMBROKERPORT']
16
+ port = port.to_i if port.kind_of?(String)
17
+ raise "root permission required for port below 1024 (#{port})" if port < 1024 && Process.euid != 0
18
+ @host = host
19
+ @port = port
20
+ opts = opts.dup
21
+ # Brokers cannot use SSL directly. If they do then when a proxy request to an SSL server is received
22
+ # the client and server will not be able to negotiate a SSL session. The proxied traffic can be SSL
23
+ # encrypted, but the SSL session will be between the client and the server.
24
+ opts[:tls] = false
25
+ @opts = opts
26
+ start_server(host, port, opts) unless @listening || ENV['PRYEMREMOTEBROKER'] || @opts[:remote_broker]
27
+ client { |c| yield self } if block_given?
28
+ end
29
+
30
+ def restart
31
+ log.info("[pry-remote-em broker] restarting on pryem://#{host}:#{port}")
32
+ @waiting = nil
33
+ @client = nil
34
+ run(@host, @port, @opts) do
35
+ PryRemoteEm.servers.each do |id, description|
36
+ next unless EM.get_sockname(description[:server])
37
+ register(
38
+ id: description[:id],
39
+ urls: description[:urls],
40
+ name: description[:name],
41
+ details: description[:details],
42
+ metrics: PryRemoteEm::Metrics.list
43
+ )
44
+ end
45
+ end
46
+ end
47
+
48
+ def opts
49
+ @opts ||= {}
50
+ end
51
+
52
+ def log
53
+ return opts[:logger] if opts[:logger]
54
+ @log ||= Logger.new(STDERR)
55
+ end
56
+
57
+ def servers
58
+ @servers ||= {}
59
+ end
60
+
61
+ def register(description)
62
+ client { |c| c.send_register_server(description[:id], description[:urls], description[:name], description[:details], description[:metrics]) }
63
+ end
64
+
65
+ def unregister(id)
66
+ client { |c| c.send_unregister_server(id) }
67
+ end
68
+
69
+ def register_server(id, description)
70
+ servers[id] = description
71
+ watch_heartbeats(id)
72
+ log.info("[pry-remote-em broker] registered #{id} #{description.inspect}")
73
+ end
74
+
75
+ def update_server(server, description)
76
+ server.update(urls: description[:urls], name: description[:name])
77
+ server[:details].update(description[:details])
78
+ server[:metrics].update(description[:metrics])
79
+ end
80
+
81
+ def unregister_server(id)
82
+ server = servers.delete(id) or return
83
+ log.warn("[pry-remote-em broker] unregister #{id} #{server.inspect}")
84
+ timer = timers.delete(id)
85
+ timer.cancel if timer
86
+ hbeats.delete(id)
87
+ end
88
+
89
+ def watch_heartbeats(id)
90
+ interval = ENV['PRYEMHBCHECK'].nil? || ENV['PRYEMHBCHECK'].empty? ? HEARTBEAT_CHECK_INTERVAL : ENV['PRYEMHBCHECK']
91
+ timers[id] ||= EM::PeriodicTimer.new(interval) do
92
+ if !hbeats[id] || (Time.new - hbeats[id]) > 20
93
+ unregister_server(id)
94
+ end
95
+ end
96
+ end
97
+
98
+ def timers
99
+ @timers ||= {}
100
+ end
101
+
102
+ def hbeats
103
+ @hbeats ||= {}
104
+ end
105
+
106
+ def connected?
107
+ @connected
108
+ end
109
+
110
+ private
111
+
112
+ def start_server(host, port, opts)
113
+ EM.start_server(host, port, PryRemoteEm::Broker, opts)
114
+ log.info("[pry-remote-em broker] listening on #{opts[:tls] ? 'pryems' : 'pryem'}://#{host}:#{port}")
115
+ @listening = true
116
+ rescue => error
117
+ if error.message.include?('port is in use')
118
+ if opts[:raise_if_port_in_use]
119
+ raise
120
+ else
121
+ # A broker is already listening on this port, we can do nothing
122
+ end
123
+ else
124
+ raise
125
+ end
126
+ end
127
+
128
+ def client(&blk)
129
+ raise ArgumentError.new('A block is required') unless block_given?
130
+ if @client
131
+ yield @client
132
+ return
133
+ end
134
+
135
+ if @waiting
136
+ @waiting << blk
137
+ else
138
+ @waiting = [blk]
139
+ EM.connect(host, port, Client::Broker, @opts) do |client|
140
+ client.errback { |e| raise(e || 'broker client error') }
141
+ client.callback do
142
+ @client = client
143
+ while (w = @waiting.shift)
144
+ w.call(client)
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end # class << self
151
+
152
+ include Proto
153
+
154
+ def receive_server_reload_list
155
+ send_server_list(Broker.servers)
156
+ end
157
+
158
+ def receive_register_server(id, urls, name, details, metrics)
159
+ @ids.push(id)
160
+ description = { urls: urls, name: name, details: details, metrics: metrics }
161
+ Broker.hbeats[id] = Time.new
162
+ server = Broker.servers[id]
163
+ if server
164
+ Broker.update_server(server, description)
165
+ else
166
+ Broker.register_server(id, description)
167
+ end
168
+ end
169
+
170
+ def receive_unregister_server(id)
171
+ server = Broker.servers[id]
172
+ Broker.unregister_server(id) if server
173
+ end
174
+
175
+ def receive_proxy_connection(url)
176
+ log.info("[pry-remote-em broker] proxying to #{url}")
177
+ url = URI.parse(url)
178
+ EM.connect(url.host, url.port, Client::Proxy, self)
179
+ end
180
+
181
+ def initialize(opts = {}, &blk)
182
+ @opts = opts
183
+ @ids = []
184
+ end
185
+
186
+ def log
187
+ Broker.log
188
+ end
189
+
190
+ def post_init
191
+ port, ip = Socket.unpack_sockaddr_in(get_peername)
192
+ log.info("[pry-remote-em broker] received client connection from #{ip}:#{port}")
193
+ send_banner("PryRemoteEm #{VERSION} #{@opts[:tls] ? 'pryems' : 'pryem'}")
194
+ @opts[:tls] ? start_tls : send_server_list(Broker.servers)
195
+ end
196
+
197
+ def start_tls
198
+ log.debug("[pry-remote-em broker] starting TLS (#{peer_ip}:#{peer_port})")
199
+ send_start_tls
200
+ super(@opts[:tls].is_a?(Hash) ? @opts[:tls] : {})
201
+ end
202
+
203
+ def peer_ip
204
+ return @peer_ip if @peer_ip
205
+ return '' if get_peername.nil?
206
+ @peer_port, @peer_ip = Socket.unpack_sockaddr_in(get_peername)
207
+ @peer_ip = '127.0.0.1' if @peer_ip == '::1' # Little hack to avoid segmentation fault in EventMachine 1.2.0.1 while connecting to PryRemoteEm Server from localhost with IPv6 address
208
+ @peer_ip
209
+ end
210
+
211
+ def peer_port
212
+ return @peer_port if @peer_port
213
+ return '' if get_peername.nil?
214
+ peer_ip # Fills peer_port too
215
+ @peer_port
216
+ end
217
+
218
+ def ssl_handshake_completed
219
+ log.info("[pry-remote-em broker] TLS connection established (#{peer_ip}:#{peer_port})")
220
+ send_server_list(Broker.servers)
221
+ end
222
+
223
+ def unbind
224
+ @ids.each do |id|
225
+ Broker.unregister_server(id)
226
+ end
227
+ end
228
+ end # module::Broker
229
+ end # module::PryRemoteEm
@@ -0,0 +1,233 @@
1
+ require 'uri'
2
+ require 'pry-remote-em'
3
+ require 'pry-remote-em/client/keyboard'
4
+ require 'pry-remote-em/client/generic'
5
+ require 'pry-remote-em/client/interactive_menu'
6
+ require 'pry'
7
+ #require 'pry-coolline' rescue require 'readline'
8
+
9
+ module PryRemoteEm
10
+ module Client
11
+ include EM::Deferrable
12
+ include Generic
13
+ include InteractiveMenu
14
+ include Pry::Helpers::BaseHelpers
15
+
16
+ class << self
17
+ def start(host = nil, port = nil, opts = {})
18
+ EM.connect(host || PryRemoteEm::DEFAULT_SERVER_HOST, port || PryRemoteEm::DEFAULT_SERVER_PORT, PryRemoteEm::Client, opts) do |c|
19
+ c.callback { yield if block_given? }
20
+ c.errback do |e|
21
+ Kernel.puts "[pry-remote-em] connection failed\n#{e}"
22
+ yield(e) if block_given?
23
+ end
24
+ end
25
+ end
26
+ end # class << self
27
+
28
+ attr_reader :opts
29
+
30
+ def initialize(opts = {})
31
+ @opts = opts
32
+ if (a = opts[:auth])
33
+ if a.respond_to?(:call)
34
+ @auth = a
35
+ else
36
+ @auth = lambda { a }
37
+ end
38
+ end
39
+ end
40
+
41
+ def post_init
42
+ @input = if defined?(PryCoolline)
43
+ PryCoolline.make_coolline
44
+ else
45
+ Pry.history.load if Pry.config.history.should_load
46
+ Readline
47
+ end
48
+ @input.completion_proc = method(:auto_complete)
49
+ end
50
+
51
+ def ssl_handshake_completed
52
+ log.info('[pry-remote-em] TLS connection established')
53
+ @opts[:tls] = true
54
+ end
55
+
56
+ def unbind
57
+ if (uri = @reconnect_to)
58
+ @reconnect_to = nil
59
+ tls = uri.scheme == 'pryems'
60
+ log.info("\033[35m[pry-remote-em] connection will not be encrypted\033[0m") if @opts[:tls] && !tls
61
+ @opts[:tls] = tls
62
+ @tls_started = false
63
+ reconnect(uri.host, uri.port)
64
+ else
65
+ @unbound = true
66
+ log.info('[pry-remote-em] session terminated')
67
+ error? ? fail : succeed
68
+ end
69
+ end
70
+
71
+ def receive_banner(name, version, scheme)
72
+ # Client::Generic#receive_banner
73
+ if super(name, version, scheme)
74
+ start_tls if @opts[:tls]
75
+ end
76
+ end
77
+
78
+ def receive_server_list(list)
79
+ if list.empty?
80
+ log.info("\033[33m[pry-remote-em] no servers are registered with the broker\033[0m")
81
+ Process.exit
82
+ end
83
+ url, proxy = choose_server(list)
84
+ return unless url
85
+ uri = URI.parse(url)
86
+ if proxy
87
+ @opts[:tls] = uri.scheme == 'pryems'
88
+ @negotiated = false
89
+ @tls_started = false
90
+ return send_proxy_connection(url)
91
+ end
92
+ @reconnect_to = uri
93
+ close_connection
94
+ end
95
+
96
+ def receive_auth(a)
97
+ return fail a if a.is_a?(String)
98
+ return authenticate if a == false
99
+ @authenticated = true if a == true
100
+ end
101
+
102
+ def receive_msg(m)
103
+ Kernel.puts "\033[1m! msg: " + m + "\033[0m"
104
+ end
105
+
106
+ def receive_msg_bcast(mb)
107
+ Kernel.puts "\033[1m!! msg: " + mb + "\033[0m"
108
+ end
109
+
110
+ def receive_shell_cmd(c)
111
+ Kernel.puts c
112
+ end
113
+
114
+ def receive_shell_result(c)
115
+ if @keyboard
116
+ @keyboard.bufferio(true)
117
+ @keyboard.close_connection
118
+ end
119
+ if c == 255 || c == 127
120
+ Kernel.puts 'command not found'
121
+ end
122
+ end
123
+
124
+ # TODO detect if the old pager behavior of Pry is supported and use it
125
+ # through Pry.pager. If it's not then use the SimplePager.
126
+ def pager
127
+ pager_class = ENV['PRYEMNOPAGER'] ? Pry::Pager::NullPager : @opts[:pager] || Pry::Pager::SimplePager
128
+ @pager ||= pager_class.new(Pry::Output.new(Pry))
129
+ end
130
+
131
+ def receive_raw(r)
132
+ pager.write(r)
133
+ rescue Pry::Pager::StopPaging
134
+ warn '[pry-remote-em] stop paging is not implemented, use PRYEMNOPAGER environment variable to avoid paging at all'
135
+ end
136
+
137
+ def receive_unknown(j)
138
+ warn "[pry-remote-em] received unexpected data: #{j.inspect}"
139
+ end
140
+
141
+ def authenticate
142
+ return fail('[pry-remote-em] authentication required') unless @auth
143
+ return fail("[pry-remote-em] can't authenticate before negotiation complete") unless @negotiated
144
+ user, pass = @auth.call
145
+ return fail("[pry-remote-em] expected #{@auth} to return a user and password") unless user && pass
146
+ send_auth([user, pass])
147
+ end # authenticate
148
+
149
+ def auto_complete(word)
150
+ word = word.completed_word if defined?(Coolline) && word.kind_of?(Coolline)
151
+
152
+ @waiting = Thread.current
153
+ EM.next_tick { send_completion(word) }
154
+ sleep
155
+ c = Thread.current[:completion]
156
+ Thread.current[:completion] = nil
157
+ c
158
+ end
159
+
160
+ def receive_completion(c)
161
+ return unless @waiting
162
+ @waiting[:completion] = c
163
+ @waiting, t = nil, @waiting
164
+ t.run
165
+ end
166
+
167
+ def receive_prompt(p)
168
+ readline(p)
169
+ end
170
+
171
+ def readline(prompt = @last_prompt)
172
+ @last_prompt = prompt
173
+ if @negotiated && !@unbound
174
+ operation = proc do
175
+ thread = Thread.current
176
+ old_trap = Signal.trap(:INT) { thread.raise Interrupt }
177
+ begin
178
+ @input.readline(prompt)
179
+ rescue Interrupt
180
+ send_clear_buffer
181
+ puts
182
+ :ignore_me
183
+ ensure
184
+ Signal.trap(:INT, old_trap)
185
+ end
186
+ end
187
+
188
+ callback = proc do |l|
189
+ next if l == :ignore_me
190
+
191
+ add_to_history(l) unless l.nil? || l.empty?
192
+
193
+ if l.nil?
194
+ readline
195
+ elsif '^^' == l[0..1]
196
+ send_msg_bcast(l[2..-1])
197
+ elsif '^' == l[0]
198
+ send_msg(l[1..-1])
199
+ elsif '.' == l[0]
200
+ send_shell_cmd(l[1..-1])
201
+ @keyboard = EM.open_keyboard(Keyboard, self)
202
+ elsif 'reset' == l.strip
203
+ # TODO work with 'bundle exec pry-remote-em ...'
204
+ # TODO work with 'ruby -I lib bin/pry-remote-em ...'
205
+ Kernel.puts "\033[1m#{$0} #{ARGV.join(' ')}\033[0m"
206
+ exec("#{$0} #{ARGV.join(' ')}")
207
+ else
208
+ send_raw(l)
209
+ end
210
+ end
211
+
212
+ EM.defer(operation, callback)
213
+ end
214
+ end # readline(prompt = @last_prompt)
215
+
216
+ def add_to_history(line)
217
+ if defined?(Readline) && @input == Readline
218
+ Readline::HISTORY.push(line)
219
+ end
220
+ # Nothing to do with Coolline, it just works
221
+ end
222
+ end # module::Client
223
+ end # module::PryRemoteEm
224
+
225
+ # TODO detect if the old pager behavior of Pry is supported and use it. If it's not
226
+ # then don't bother adding a pager accessor
227
+ # Pry::Helpers::BaseHelpers#stagger_output expects Pry.pager to be defined
228
+ class Pry
229
+ class << self
230
+ attr_accessor :pager unless respond_to?(:pager)
231
+ end
232
+ end
233
+ Pry.pager = true