pry-remote-em 0.7.5 → 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.
@@ -1,4 +1,4 @@
1
- require "pry-remote-em/client/generic"
1
+ require 'pry-remote-em/client/generic'
2
2
 
3
3
  module PryRemoteEm
4
4
  module Client
@@ -22,11 +22,14 @@ module PryRemoteEm
22
22
  end
23
23
 
24
24
  def unbind
25
- log.info("[pry-remote-em broker-client] broker connection unbound starting a new one")
25
+ return if EventMachine.stopping?
26
+
26
27
  # Give the existing broker a little time to release the port. Even if the
27
28
  # restart here fails the next time a server tries to register, a new client
28
29
  # will be created; when that fails Broker#restart will be called again.
29
- EM::Timer.new(rand(0.9)) do
30
+ timeout = ENV['PRYEMBROKERTIMEOUT'].nil? || ENV['PRYEMBROKERTIMEOUT'].empty? ? RECONNECT_TO_BROKER_TIMEOUT : ENV['PRYEMBROKERTIMEOUT']
31
+ log.info("[pry-remote-em broker-client] broker connection unbound; starting a new one in a #{timeout} seconds")
32
+ EM::Timer.new(timeout) do
30
33
  PryRemoteEm::Broker.restart
31
34
  end
32
35
  end
@@ -1,4 +1,4 @@
1
- require "pry-remote-em/proto"
1
+ require 'pry-remote-em/proto'
2
2
 
3
3
  module PryRemoteEm
4
4
  module Client
@@ -6,7 +6,7 @@ module PryRemoteEm
6
6
  include EM::Deferrable
7
7
  include Proto
8
8
 
9
- def initialize(opt = {})
9
+ def initialize(opts = {})
10
10
  @opts = opts
11
11
  end
12
12
 
@@ -27,7 +27,7 @@ module PryRemoteEm
27
27
  def start_tls
28
28
  return if @tls_started
29
29
  @tls_started = true
30
- log.info("[pry-remote-em] negotiating TLS")
30
+ log.info('[pry-remote-em] negotiating TLS')
31
31
  super(opts[:tls].is_a?(Hash) ? opts[:tls] : {})
32
32
  end
33
33
 
@@ -37,10 +37,11 @@ module PryRemoteEm
37
37
  log.info("[pry-remote-em] client connected to pryem://#{ip}:#{port}/")
38
38
  else
39
39
  # TODO use the args used to create this connection
40
- log.info("[pry-remote-em] client connected")
40
+ log.info('[pry-remote-em] client connected')
41
41
  end
42
- @nego_timer = EM::Timer.new(PryRemoteEm::NEGOTIMER) do
43
- fail("[pry-remote-em] server didn't finish negotiation within #{PryRemoteEm::NEGOTIMER} seconds; terminating")
42
+ timeout = ENV['PRYEMNEGOTIMEOUT'].nil? || ENV['PRYEMNEGOTIMEOUT'].empty? ? NEGOTIATION_TIMEOUT : ENV['PRYEMNEGOTIMEOUT']
43
+ @nego_timer = EM::Timer.new(timeout) do
44
+ fail("[pry-remote-em] server didn't finish negotiation within #{timeout} seconds; terminating")
44
45
  end
45
46
  end
46
47
 
@@ -0,0 +1,171 @@
1
+ require 'highline'
2
+
3
+ module PryRemoteEm
4
+ module Client
5
+ module InteractiveMenu
6
+ def choose_server(list)
7
+ highline = HighLine.new
8
+ choice = nil
9
+ url = nil
10
+ nm_col_len = list.map { |id, server| server['name'].size }.max
11
+ sc_col = list.map { |id, server| second_column_for_server(server) }
12
+ sc_col_name = opts[:show_details] == '@' ? 'details' : opts[:show_details] || 'url'
13
+ sc_col_len = [sc_col.flatten.map(&:size).max || 1, sc_col_name.size].max
14
+ header = sprintf("| %-3s | %-#{nm_col_len}s | %-#{sc_col_len}s |", '', 'name', sc_col_name)
15
+ border = ('-' * header.length)
16
+ table = [border, header, border]
17
+ list = list.to_a
18
+ list = filter_server_list(list)
19
+ list = sort_server_list(list)
20
+ list.each.with_index do |(id, server), index|
21
+ column = second_column_for_server(server)
22
+
23
+ table << sprintf("| %-2d | %-#{nm_col_len}s | %-#{sc_col_len}s |", index + 1, server['name'], column.first)
24
+
25
+ if column.size > 1
26
+ column[1..-1].each do |element|
27
+ table << sprintf("| %-2s | %-#{nm_col_len}s | %-#{sc_col_len}s |", '', '', element)
28
+ end
29
+ end
30
+ end
31
+ table << border
32
+ table = table.join("\n")
33
+ Kernel.puts table
34
+
35
+ proxy = if (choice = opts.delete(:proxy))
36
+ true
37
+ elsif (choice = opts.delete(:connect))
38
+ false
39
+ elsif opts.delete(:proxy_by_default)
40
+ true
41
+ else
42
+ false
43
+ end
44
+
45
+ while url.nil?
46
+ if proxy
47
+ question = "(q) to quit; (r) to refresh; (c) to connect without proxy\nproxy to: "
48
+ else
49
+ question = "(q) to quit; (r) to refresh; (p) to proxy\nconnect to: "
50
+ end
51
+
52
+ choice = highline.ask(question)
53
+
54
+ return close_connection if ['q', 'quit', 'exit'].include?(choice.downcase)
55
+ if ['r', 'reload', 'refresh'].include?(choice.downcase)
56
+ send_server_reload_list
57
+ return nil
58
+ end
59
+ if ['c', 'connect'].include?(choice.downcase)
60
+ proxy = false
61
+ choice = nil
62
+ next
63
+ end
64
+ if ['p', 'proxy'].include?(choice.downcase)
65
+ proxy = true
66
+ choice = nil
67
+ next
68
+ end
69
+
70
+ choice = choice[/^\d+$/] ?
71
+ list[choice.to_i - 1] :
72
+ list.detect { |(id, server)| choice == id || choice == server['name'] || server['urls'].include?(choice) }
73
+
74
+ if choice
75
+ id, server = *choice
76
+ urls = filtered_urls_list_for_server(server)
77
+ url = if urls.size > 1
78
+ choose_url(urls)
79
+ elsif urls.size == 1
80
+ urls.first
81
+ else
82
+ log.error("\033[31mno #{'non-localhost ' if opts[:ignore_localhost]}urls for this server\033[0m")
83
+ nil
84
+ end
85
+ else
86
+ log.error("\033[31mserver not found\033[0m")
87
+ end
88
+ end
89
+
90
+ return url, proxy
91
+ end
92
+
93
+ def choose_url(urls)
94
+ highline = HighLine.new
95
+ url = nil
96
+ length = urls.map(&:size).max
97
+ border = '-' * (length + 8)
98
+ Kernel.puts border
99
+ urls.each.with_index do |url, index|
100
+ Kernel.puts sprintf("| %d | %-#{length}s |", index + 1, url)
101
+ end
102
+ Kernel.puts border
103
+
104
+ choice = highline.ask('select url: ')
105
+
106
+ url = if choice && choice[/^\d+$/]
107
+ urls[choice.to_i - 1]
108
+ elsif urls.include?(choice)
109
+ choice
110
+ end
111
+
112
+ log.error("\033[31mno url selected\033[0m") unless url
113
+
114
+ return url
115
+ end
116
+
117
+ def sort_server_list(list)
118
+ case opts[:sort]
119
+ when :name
120
+ list.sort { |(_, a), (_, b)| a['name'] <=> b['name'] }
121
+ when :ssl
122
+ list.sort &sort_by_uri(:scheme)
123
+ when :port
124
+ list.sort &sort_by_uri(:port)
125
+ else # :host or default
126
+ list.sort &sort_by_uri(:host)
127
+ end
128
+ end
129
+
130
+ def sort_by_uri(part)
131
+ -> a, b { URI.parse(a[1]['urls'].first).send(part) <=> URI.parse(b[1]['urls'].first).send(part) }
132
+ end
133
+
134
+ def filter_server_list(list)
135
+ if opts[:filter_host]
136
+ list = list.select { |(id, server)| server['urls'].any? { |url| URI.parse(url).host =~ opts[:filter_host] } }
137
+ end
138
+ if opts[:filter_name]
139
+ list = list.select { |(id, server)| server['name'] =~ opts[:filter_name] }
140
+ end
141
+ if opts.has_key?(:filter_ssl)
142
+ target_scheme = opts[:filter_ssl] ? 'pryems' : 'pryem'
143
+ list = list.select { |(id, server)| server['urls'].any? { |url| URI.parse(url).scheme == target_scheme } }
144
+ end
145
+ if list.empty?
146
+ log.info("\033[33m[pry-remote-em] no registered servers match the given filter\033[0m")
147
+ Process.exit
148
+ end
149
+ list
150
+ end
151
+
152
+ def second_column_for_server(server)
153
+ column = case opts[:show_details]
154
+ when nil then filtered_urls_list_for_server(server)
155
+ when '@' then server['details']
156
+ else server['details'][opts[:show_details]]
157
+ end
158
+
159
+ case column
160
+ when Array then column.map(&:to_s)
161
+ when Hash then column.map { |key, value| "#{key}: #{value}" }
162
+ else [column.to_s]
163
+ end
164
+ end
165
+
166
+ def filtered_urls_list_for_server(server)
167
+ opts[:ignore_localhost] ? server['urls'].reject { |url| %w[localhost 127.0.0.1 ::1].include?(URI.parse(url).host) } : server['urls']
168
+ end
169
+ end # module::InteractiveMenu
170
+ end # module::Client
171
+ end # module PryRemoteEm
@@ -1,4 +1,5 @@
1
- require "termios"
1
+ require 'termios'
2
+
2
3
  module PryRemoteEm
3
4
  module Client
4
5
  module Keyboard
@@ -7,34 +8,30 @@ module PryRemoteEm
7
8
  @con = c
8
9
  # TODO check actual current values to determine if it's enabled or not
9
10
  @buff_enabled = true
10
- # On EM < 1.0.0.beta.4 the keyboard handler and Termios don't work well together
11
- # readline will complain that STDIN isn't a tty after Termios manipulation, so
12
- # just don't let it happen
13
- @manip_buff = Gem.loaded_specs["eventmachine"].version >= Gem::Version.new("1.0.0.beta.4")
11
+
14
12
  bufferio(false)
15
- # TODO retain the old SIGINT handler and reset it later
16
- trap :SIGINT do
17
- @con.send_data({:ssc => true})
13
+
14
+ @old_trap = Signal.trap(:INT) do
15
+ @con.send_shell_sig(:int)
18
16
  end
19
17
  end
20
18
 
21
19
  def receive_data(d)
22
- @con.send_data({:sd => d})
20
+ print d.chr
21
+ @con.send_shell_data(d)
23
22
  end
24
23
 
25
24
  def unbind
26
25
  bufferio(true)
27
- trap :SIGINT do
28
- Process.exit
29
- end
26
+
27
+ Signal.trap(:INT, @old_trap)
30
28
  end
31
29
 
32
30
  # Makes stdin buffered or unbuffered.
33
31
  # In unbuffered mode read and select will not wait for "\n"; also will not echo characters.
34
32
  # This probably does not work on Windows.
35
- # On EventMachine < 1.0.0.beta.4 this method doesn't do anything
36
33
  def bufferio(enable)
37
- return if !@manip_buff || (enable && @buff_enabled) || (!enable && !@buff_enabled)
34
+ return if (enable && @buff_enabled) || (!enable && !@buff_enabled)
38
35
  attr = Termios.getattr($stdin)
39
36
  enable ? (attr.c_lflag |= Termios::ICANON | Termios::ECHO) : (attr.c_lflag &= ~(Termios::ICANON|Termios::ECHO))
40
37
  Termios.setattr($stdin, Termios::TCSANOW, attr)
@@ -43,4 +40,3 @@ module PryRemoteEm
43
40
  end # module::Keyboard
44
41
  end # module::Client
45
42
  end # module PryRemoteEm
46
-
@@ -1,4 +1,5 @@
1
- require "pry-remote-em/client/generic"
1
+ require 'pry-remote-em/client/generic'
2
+
2
3
  module PryRemoteEm
3
4
  module Client
4
5
  module Proxy
@@ -13,7 +14,7 @@ module PryRemoteEm
13
14
  port, ip = Socket.unpack_sockaddr_in(get_peername)
14
15
  log.info("[pry-remote-em] proxy connected to pryem://#{ip}:#{port}/")
15
16
  else
16
- log.info("[pry-remote-em] proxy connected")
17
+ log.info('[pry-remote-em] proxy connected')
17
18
  end
18
19
  @client.proxy_incoming_to(self)
19
20
  proxy_incoming_to(@client)
@@ -1,59 +1,23 @@
1
- require 'json'
2
- require "zlib"
1
+ # Prefer MessagePack "out of the box" protocol over old JSON+Zlib+CRC
2
+ # variant because of strange `expected "PRYEM" not "}PRYE"` errors
3
+ # on long output over network (not localhost).
4
+ require 'msgpack'
3
5
 
4
6
  module PryRemoteEm
5
7
  module Proto
6
- PREAMBLE = 'PRYEM'
7
- SEPERATOR = ' '
8
- PREAMBLE_LEN = PREAMBLE.bytesize
9
- SEPERATOR_LEN = SEPERATOR.bytesize
10
-
11
- def send_json(d)
12
- send_data(JSON.dump(d.is_a?(String) ? {:d => d} : d))
13
- end
14
-
15
- def send_data(data)
16
- crc = Zlib::crc32(data).to_s
17
- msg = PREAMBLE + (data.bytesize + crc.bytesize + SEPERATOR_LEN).to_s + SEPERATOR + crc + SEPERATOR + data
18
- super(msg)
8
+ def receive_data(data)
9
+ @unpacker ||= MessagePack::Unpacker.new
10
+ @unpacker.feed_each(data) { |object| receive_object(object) }
19
11
  end
20
12
 
21
- # Each frame is a string consisting of 4 parts
22
- # 1. preamble (PRYEM)
23
- # 2. length in characters of crc, a seperator, and body
24
- # 3. CRC
25
- # 4. JSON encoded body
26
- # It is possible and likely that receive_data will be given more than one frame at a time, or
27
- # an incomplete frame.
28
- # @example "PRYEM42 3900082256 {\"g\":\"PryRemoteEm 0.7.0 pryem\"}PRYEM22 1794245389 {\"a\":false}"
29
- def receive_data(d)
30
- return unless d && d.bytesize > 0
31
- @buffer ||= "" # inlieu of a post_init
32
- @buffer << d
33
- while @buffer && !@buffer.empty?
34
- return unless @buffer.bytesize >= PREAMBLE_LEN &&
35
- (len_ends = @buffer.index(SEPERATOR)) &&
36
- (crc_ends = @buffer.index(SEPERATOR, len_ends))
37
- if (preamble = @buffer[0...PREAMBLE_LEN]) != PREAMBLE
38
- raise "message is not in proper format; expected #{PREAMBLE.inspect} not #{preamble.inspect}"
39
- end
40
- length = @buffer[PREAMBLE_LEN ... len_ends].to_i
41
- return if len_ends + length > @buffer.bytesize
42
- crc_start = len_ends + SEPERATOR_LEN
43
- crc, data = @buffer[crc_start ... crc_start + length].split(SEPERATOR, 2)
44
- crc = crc.to_i
45
- @buffer = @buffer[crc_start + length .. -1]
46
- if (dcrc = Zlib::crc32(data)) == crc
47
- receive_json(JSON.load(data))
48
- else
49
- warn("data crc #{dcrc} doesn't match crc #{crc.inspect}; discarding #{data.inspect}")
50
- end
51
- end
52
- @buffer
13
+ def send_object(object)
14
+ send_data(object.to_msgpack)
53
15
  end
54
16
 
55
- def receive_json(j)
56
- if j['p']
17
+ def receive_object(j)
18
+ if !j.is_a?(Hash)
19
+ receive_unknown(j)
20
+ elsif j['p']
57
21
  receive_prompt(j['p'])
58
22
  elsif j['d']
59
23
  receive_raw(j['d'])
@@ -66,23 +30,27 @@ module PryRemoteEm
66
30
  elsif j.include?('sc')
67
31
  receive_shell_result(j['sc'])
68
32
  elsif j['g']
69
- receive_banner(*j['g'].split(" ", 3))
33
+ receive_banner(*j['g'].split(' ', 3))
70
34
  elsif j['c']
71
35
  receive_completion(j['c'])
36
+ elsif j['cb']
37
+ receive_clear_buffer
72
38
  elsif j.include?('a')
73
39
  receive_auth(*Array(j['a']))
74
40
  elsif j['sd']
75
41
  receive_shell_data(j['sd'])
76
42
  elsif j['ssc']
77
- receive_shell_sig(:term)
43
+ receive_shell_sig(j['ssc'].to_sym)
78
44
  elsif j['hb']
79
45
  receive_heartbeat(j['hb'])
80
46
  elsif j['rs']
81
47
  receive_register_server(*Array(j['rs']))
82
48
  elsif j['urs']
83
49
  receive_unregister_server(j['urs'])
84
- elsif j.include?('sl')
85
- j['sl'] ? receive_server_list(j['sl']) : receive_server_list
50
+ elsif j['sl']
51
+ receive_server_list(j['sl'])
52
+ elsif j['srl']
53
+ receive_server_reload_list
86
54
  elsif j['tls']
87
55
  receive_start_tls
88
56
  elsif j['pc']
@@ -90,7 +58,6 @@ module PryRemoteEm
90
58
  else
91
59
  receive_unknown(j)
92
60
  end
93
- j
94
61
  end
95
62
 
96
63
 
@@ -101,67 +68,81 @@ module PryRemoteEm
101
68
  def receive_msg_bcast(mb); end
102
69
  def receive_shell_cmd(c); end
103
70
  def receive_shell_result(c); end
104
- def receive_completion(c); end
105
- def receive_raw(r); end
106
71
  def receive_shell_sig(sym); end
107
72
  def receive_shell_data(d); end
73
+ def receive_completion(c); end
74
+ def receive_clear_buffer; end
75
+ def receive_raw(r); end
108
76
  def receive_unknown(j); end
109
77
 
110
78
  def receive_start_tls; end
111
79
 
112
- def receive_register_server(url, name); end
113
- def receive_unregister_server(url); end
114
- def receive_server_list(list = nil); end
80
+ def receive_register_server(id, urls, name, details); end
81
+ def receive_unregister_server(id); end
82
+ def receive_server_list(list); end
83
+ def receive_server_reload_list; end
115
84
 
116
85
  def receive_proxy_connection(url); end
117
86
 
118
87
  def send_banner(g)
119
- send_json({:g => g})
88
+ send_object({g: g})
120
89
  end
121
90
  def send_auth(a)
122
- send_json({:a => a})
91
+ send_object({a: a})
123
92
  end
124
93
  def send_prompt(p)
125
- send_json({:p => p})
94
+ send_object({p: p})
126
95
  end
127
96
  def send_msg_bcast(m)
128
- send_json({:mb => m})
97
+ send_object({mb: m})
129
98
  end
130
99
  def send_msg(m)
131
- send_json({:m => m})
100
+ send_object({m: m})
132
101
  end
133
102
  def send_shell_cmd(c)
134
- send_json({:s => c})
103
+ send_object({s: c})
135
104
  end
136
105
  def send_shell_result(r)
137
- send_json({:sc => r})
106
+ send_object({sc: r})
107
+ end
108
+ def send_shell_sig(sym)
109
+ send_object({ssc: sym})
110
+ end
111
+ def send_shell_data(d)
112
+ send_object({sd: d})
138
113
  end
139
114
  def send_completion(word)
140
- send_json({:c => word})
115
+ send_object({c: word})
116
+ end
117
+ def send_clear_buffer
118
+ send_object({cb: true})
141
119
  end
142
- def send_raw(l)
143
- send_json(l)
120
+ def send_raw(d)
121
+ send_object(d.is_a?(String) ? {d: d} : d)
144
122
  end
145
123
 
146
124
  def send_start_tls
147
- send_json({:tls => true})
125
+ send_object({tls: true})
148
126
  end
149
127
 
150
- def send_register_server(url, name)
151
- send_json({:rs => [url, name]})
128
+ def send_register_server(id, urls, name, details)
129
+ send_object({rs: [id, urls, name, details]})
152
130
  end
153
- def send_unregister_server(url)
154
- send_json({:urs => url})
131
+ def send_unregister_server(id)
132
+ send_object({urs: id})
155
133
  end
156
134
  def send_heatbeat(url)
157
- send_json({:hb => url})
135
+ send_object({hb: url})
158
136
  end
159
137
  def send_server_list(list = nil)
160
- send_json({:sl => list})
138
+ send_object({sl: list})
139
+ end
140
+ def send_server_reload_list
141
+ send_object({srl: true})
161
142
  end
162
143
 
163
144
  def send_proxy_connection(url)
164
- send_json({:pc => url})
145
+ send_object({pc: url})
165
146
  end
166
147
  end # module::Proto
167
148
  end # module::PryRemoteEm