live_console 0.1.0 → 0.2.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/bin/udscat +51 -0
- data/doc/README +63 -20
- data/doc/lc_example.rb +6 -2
- data/doc/lc_unix_example.rb +37 -0
- data/lib/live_console/io_methods/socket_io.rb +27 -0
- data/lib/live_console/io_methods/unix_socket_io.rb +31 -0
- data/lib/live_console/io_methods.rb +53 -0
- data/lib/live_console.rb +122 -54
- data/lib/live_console_config.rb +2 -1
- metadata +52 -37
data/bin/udscat
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This is a client for the unix domain socket version of LiveConsole. It just
|
4
|
+
# talks to the socket. It has been included so that you don't have to track
|
5
|
+
# down a version of netcat that does this.
|
6
|
+
|
7
|
+
require 'socket'
|
8
|
+
|
9
|
+
socket_path = ARGV.first
|
10
|
+
if socket_path.nil?
|
11
|
+
$stderr.puts "You must supply a path to the socket you want to connect to."
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
|
15
|
+
client = UNIXSocket.new socket_path
|
16
|
+
|
17
|
+
$stdin.sync = $stdout.sync = client.sync = true
|
18
|
+
|
19
|
+
read_thread =
|
20
|
+
Thread.new {
|
21
|
+
loop {
|
22
|
+
begin
|
23
|
+
Thread.pass
|
24
|
+
$stdout.write client.read_nonblock(1024)
|
25
|
+
rescue Errno::EAGAIN, Errno::EINTR => e
|
26
|
+
IO.select [client], [], [], 1
|
27
|
+
rescue Errno::EOFError, Errno::EPIPE => e
|
28
|
+
# nothing
|
29
|
+
end
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
Thread.new {
|
34
|
+
loop {
|
35
|
+
begin
|
36
|
+
l = $stdin.read_nonblock(1024)
|
37
|
+
rescue Errno::EAGAIN
|
38
|
+
retry
|
39
|
+
end
|
40
|
+
|
41
|
+
begin
|
42
|
+
client.print l if IO.select [], [client], [], 1
|
43
|
+
rescue Errno::EPIPE => e
|
44
|
+
$stderr.puts "Other end closed."
|
45
|
+
exit 0
|
46
|
+
end
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
trap('INT') { exit 0 }
|
51
|
+
loop { sleep 1 }
|
data/doc/README
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
|
3
3
|
== Summary
|
4
4
|
|
5
|
-
LiveConsole is a library for providing IRB over a TCP connection
|
6
|
-
it to your application, you can run arbitrary code
|
5
|
+
LiveConsole is a library for providing IRB over a TCP connection or a Unix
|
6
|
+
Domain Socket. If you add it to your application, you can run arbitrary code
|
7
|
+
against your application.
|
7
8
|
For example, you can:
|
8
9
|
* Inspect the state of a running application
|
9
10
|
* Change the state of the application
|
10
11
|
* Patch code on the fly, without a restart.
|
11
|
-
* Let anyone on the net 0wn you if you bind to
|
12
|
-
|
13
|
-
|
12
|
+
* Let anyone on the net 0wn you if you bind to a public interface. :)
|
13
|
+
It's useful as a diagnostic tool, a debugging tool, and a way to impress your
|
14
|
+
friends or get those Lisp guys off your back. You know the ones I mean.
|
14
15
|
|
15
16
|
== Stern Security Warning. Grrr.
|
16
17
|
|
@@ -35,14 +36,32 @@ LiveConsole is very easy to use in your own app:
|
|
35
36
|
require 'rubygems'
|
36
37
|
require 'live_console'
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
39
|
+
# Create a LiveConsole using TCP on port 1337
|
40
|
+
lc = LiveConsole.new :socket, :port => 1337
|
41
|
+
# We're not yet accepting connections. We need to start it up:
|
42
|
+
lc.start # Starts the LiveConsole thread
|
41
43
|
# At this point, users can connect and get an IRB prompt.
|
42
|
-
lc.stop
|
44
|
+
lc.stop # Kills the LiveConsole thread
|
43
45
|
# Now, no one can connect.
|
44
46
|
|
45
|
-
|
47
|
+
# Create a LiveConsole using a Unix socket in /tmp/live-console.sock
|
48
|
+
lc = LiveConsole.new :unix_socket, :path => '/tmp/live-console.sock'
|
49
|
+
# As above:
|
50
|
+
lc.start
|
51
|
+
lc.stop
|
52
|
+
|
53
|
+
# Have a LiveConsole run code in a binding other than the top-level:
|
54
|
+
lc = LiveConsole.new :unix_socket, :path => '/tmp/live-console.sock'
|
55
|
+
:bind => binding
|
56
|
+
lc.start
|
57
|
+
# That will start IRB in the current binding. There is also an accessor:
|
58
|
+
lc.bind = binding
|
59
|
+
# Of course, you must restart before IRB will see the new binding:
|
60
|
+
lc.restart
|
61
|
+
|
62
|
+
Have a look at doc/lc_example.rb or doc/lc_unix_example.rb for brief examples
|
63
|
+
of how to use LiveConsole.
|
64
|
+
|
46
65
|
Try just running it:
|
47
66
|
|
48
67
|
$ ruby doc/lc_example.rb 4000 test
|
@@ -50,30 +69,54 @@ Try just running it:
|
|
50
69
|
$ netcat localhost 4000
|
51
70
|
irb(main):001:0> puts 'Wow, magic!'
|
52
71
|
|
72
|
+
$ ruby doc/lc_unix_example.rb /tmp/live-console.sock
|
73
|
+
# Then, in a different shell:
|
74
|
+
$ udscat /tmp/live-console.sock
|
75
|
+
irb(main):001:0> puts 'Words cannot describe the joy I feel.'
|
76
|
+
|
53
77
|
You can get creative about it, only starting LiveConsole when there's an
|
54
78
|
unhandled exception in your server, and then calling LiveConsole#stop when
|
55
79
|
you've diagnosed and fixed whatever the problem was.
|
56
80
|
|
81
|
+
Additionally, if you want to run LiveConsole on a server, but run netcat
|
82
|
+
locally, you can use SSH port forwarding to avoid having to open LiveConsole
|
83
|
+
to the world:
|
84
|
+
|
85
|
+
ssh -L4000:localhost:4000 you@server
|
86
|
+
|
87
|
+
Then, locally, you can do
|
88
|
+
|
89
|
+
netcat localhost 4000
|
90
|
+
|
91
|
+
and get the remote LiveConsole. man ssh for more details. Of course, this
|
92
|
+
only works for the TCP socket mode.
|
93
|
+
|
57
94
|
== Bugs
|
58
95
|
|
59
96
|
LiveConsole lacks many of the niceties of IRB on the console, like Readline
|
60
97
|
support.
|
61
98
|
|
62
99
|
Typing exit, hitting ^D, or sending signals (like INT or STOP) doesn't work.
|
63
|
-
Just exit the program you used to connect to it.
|
100
|
+
Just exit the program you used to connect to it. This has more to do with the
|
101
|
+
program you use to connect to the socket.
|
102
|
+
|
103
|
+
For TCP connections, there is no authentication support yet, although it is
|
104
|
+
planned for the near future. This creates a security risk: anyone that can
|
105
|
+
connect to the socket can run arbitrary Ruby code as the user who owns the
|
106
|
+
process. In fact, even binding to localhost can be a security issue if you're
|
107
|
+
on a box with any untrusted users. If there's a chance you don't know what
|
108
|
+
you're doing, avoid using this library. The Unix Domain Socket version is more
|
109
|
+
secure, as you can control access via filesystem permissions.
|
64
110
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
binding to localhost can be a security issue if you're on a box with any
|
69
|
-
untrusted users. If there's a chance you don't know what you're doing, avoid
|
70
|
-
using this library.
|
111
|
+
Only one client can connect at a time. I don't think anyone needs multiple LC
|
112
|
+
connections to serve multiple instances of IRB to various clients, but if you
|
113
|
+
need it, let me know.
|
71
114
|
|
72
115
|
The README contains a slur against Lisp guys. Please stop hitting me with that PDP-10 manual. I love your language and the lambda tattoo on your chest.
|
73
116
|
|
74
|
-
Other than that, LiveConsole doesn't have any known bugs, but it is
|
75
|
-
software, so they are likely to be there. Bug
|
76
|
-
accepted.
|
117
|
+
Other than that, LiveConsole doesn't have any known bugs, but it is odd
|
118
|
+
software that also monkey-patches IRB, so they are likely to be there. Bug
|
119
|
+
reports and patches gratefully accepted.
|
77
120
|
|
78
121
|
== Credits
|
79
122
|
|
data/doc/lc_example.rb
CHANGED
@@ -14,14 +14,18 @@ and then in a different terminal, connect to it via netcat or telnet. You can
|
|
14
14
|
check that the value of $x is exactly what you set it to, and that you're
|
15
15
|
working inside this process, but there's not much to do inside the example
|
16
16
|
script. :)
|
17
|
+
|
17
18
|
EOF
|
18
19
|
|
19
20
|
port = ARGV.first.to_i
|
20
21
|
port = port.zero? ? 3333 : port
|
21
22
|
$x = ARGV[1]
|
22
23
|
|
23
|
-
lc = LiveConsole.new port
|
24
|
-
lc.
|
24
|
+
lc = LiveConsole.new :socket, :port => port, :bind => binding
|
25
|
+
lc.start
|
26
|
+
|
27
|
+
puts "My PID is #{Process.pid}, " \
|
28
|
+
"I'm running on port #{port}, and $x = #{$x.inspect}"
|
25
29
|
|
26
30
|
oldx = $x
|
27
31
|
loop {
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'live_console'
|
5
|
+
|
6
|
+
default_path = "/tmp/lc_example_#{Process.uid}.sock"
|
7
|
+
|
8
|
+
print <<-EOF
|
9
|
+
This is a demo program for LiveConsole. It starts a LiveConsole at the
|
10
|
+
specified path, and you can connect to it by using netcat or telnet to connect
|
11
|
+
to the specified port.
|
12
|
+
Usage:
|
13
|
+
#{$0} [path_to_socket [value_for_$x]]
|
14
|
+
The default path is #{default_path}, and $x is set by default to nil.
|
15
|
+
Run this program, and then in a different terminal, connect to it via
|
16
|
+
the supplied udscat program or the BSD version of netcat.
|
17
|
+
EOF
|
18
|
+
|
19
|
+
path = ARGV.first
|
20
|
+
path = path.nil? ? default_path : path
|
21
|
+
$x = ARGV[1]
|
22
|
+
|
23
|
+
lc = LiveConsole.new :unix_socket, :path => path, :bind => binding
|
24
|
+
lc.start
|
25
|
+
|
26
|
+
puts "My PID is #{Process.pid}, " \
|
27
|
+
"I'm running on #{path}, and $x = #{$x.inspect}"
|
28
|
+
|
29
|
+
oldx = $x
|
30
|
+
loop {
|
31
|
+
if $x != oldx
|
32
|
+
puts "The time is now #{Time.now.strftime('%R:%S')}.",
|
33
|
+
"The value of $x changed from #{oldx.inspect} to #{$x.inspect}."
|
34
|
+
oldx = $x
|
35
|
+
end
|
36
|
+
sleep 1
|
37
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class LiveConsole::IOMethods::SocketIO
|
2
|
+
DefaultOpts = {
|
3
|
+
:host => '127.0.0.1',
|
4
|
+
}.freeze
|
5
|
+
RequiredOpts = DefaultOpts.keys + [:port]
|
6
|
+
|
7
|
+
include LiveConsole::IOMethods::IOMethod
|
8
|
+
|
9
|
+
def start
|
10
|
+
@server ||= TCPServer.new host, port
|
11
|
+
|
12
|
+
begin
|
13
|
+
self.raw_input = self.raw_output = server.accept_nonblock
|
14
|
+
return true
|
15
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
|
16
|
+
Errno::EINTR => e
|
17
|
+
select
|
18
|
+
retry
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop
|
23
|
+
select
|
24
|
+
raw_input.close rescue nil
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
class LiveConsole::IOMethods::UnixSocketIO
|
4
|
+
DefaultOpts = {
|
5
|
+
:mode => 0600,
|
6
|
+
:uid => Process.uid,
|
7
|
+
:gid => Process.gid,
|
8
|
+
}
|
9
|
+
RequiredOpts = DefaultOpts.keys + [:path]
|
10
|
+
|
11
|
+
include LiveConsole::IOMethods::IOMethod
|
12
|
+
|
13
|
+
def start
|
14
|
+
@server ||= UNIXServer.new path
|
15
|
+
|
16
|
+
begin
|
17
|
+
self.raw_input = self.raw_output = server.accept_nonblock
|
18
|
+
raw_input.sync = true
|
19
|
+
return true
|
20
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
|
21
|
+
Errno::EINTR => e
|
22
|
+
select
|
23
|
+
retry
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
select
|
29
|
+
raw_input.close
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module LiveConsole::IOMethods
|
2
|
+
List = []
|
3
|
+
|
4
|
+
Dir[File.join(File.dirname(__FILE__), 'io_methods', '*.rb')].each { |entry|
|
5
|
+
fname = entry.sub /\.rb$/, ''
|
6
|
+
classname = File.basename(entry, '.rb').capitalize.
|
7
|
+
gsub(/_(\w)/) { $1.upcase }.sub(/io$/i, 'IO').to_sym
|
8
|
+
mname = File.basename(fname).sub(/_io$/, '').to_sym
|
9
|
+
|
10
|
+
autoload classname, fname
|
11
|
+
List << mname
|
12
|
+
|
13
|
+
define_method(mname) {
|
14
|
+
const_get classname
|
15
|
+
}
|
16
|
+
}
|
17
|
+
List.freeze
|
18
|
+
|
19
|
+
extend self
|
20
|
+
|
21
|
+
module IOMethod
|
22
|
+
def initialize(opts)
|
23
|
+
self.opts = self.class::DefaultOpts.merge opts
|
24
|
+
unless missing_opts.empty?
|
25
|
+
raise ArgumentError, "Missing opts for " \
|
26
|
+
"#{self.class.name}: #{missing_opts.inspect}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def missing_opts
|
31
|
+
self.class::RequiredOpts - opts.keys
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.included(other)
|
35
|
+
other.instance_eval {
|
36
|
+
readers = [:opts, :raw_input, :raw_output]
|
37
|
+
attr_accessor *readers
|
38
|
+
private *readers.map { |r| (r.to_s + '=').to_sym }
|
39
|
+
|
40
|
+
other::RequiredOpts.each { |opt|
|
41
|
+
define_method(opt) { opts[opt] }
|
42
|
+
}
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def select
|
47
|
+
IO.select [server], [], [], 1 if server
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
attr_accessor :server
|
52
|
+
end
|
53
|
+
end
|
data/lib/live_console.rb
CHANGED
@@ -5,73 +5,119 @@
|
|
5
5
|
|
6
6
|
require 'irb'
|
7
7
|
require 'socket'
|
8
|
+
require 'live_console_config'
|
8
9
|
|
9
10
|
# LiveConsole provides a socket that can be connected to via netcat or telnet
|
10
11
|
# to use to connect to an IRB session inside a running process. It creates a
|
11
|
-
# thread that listens on the specified address/port
|
12
|
-
# clients with an IRB shell. Using this, you can
|
13
|
-
# instance of a Ruby process to inspect the state or
|
14
|
-
# fly. There is currently no readline support.
|
12
|
+
# thread that listens on the specified address/port or Unix Domain Socket path,
|
13
|
+
# and presents connecting clients with an IRB shell. Using this, you can
|
14
|
+
# execute code on a running instance of a Ruby process to inspect the state or
|
15
|
+
# even patch code on the fly. There is currently no readline support.
|
15
16
|
class LiveConsole
|
16
17
|
include Socket::Constants
|
18
|
+
autoload :IOMethods, 'live_console/io_methods'
|
17
19
|
|
18
|
-
attr_accessor :
|
19
|
-
private :
|
20
|
+
attr_accessor :io_method, :io, :thread, :bind
|
21
|
+
private :io_method=, :io=, :thread=
|
20
22
|
|
21
23
|
# call-seq:
|
22
|
-
# # Bind a LiveConsole to localhost:3030
|
23
|
-
#
|
24
|
+
# # Bind a LiveConsole to localhost:3030 (only allow clients on this
|
25
|
+
# # machine to connect):
|
26
|
+
# LiveConsole.new :socket, :port => 3030
|
24
27
|
# # Accept connections from anywhere on port 3030. Ridiculously insecure:
|
25
|
-
#
|
28
|
+
# LiveConsole.new(:socket, :port => 3030, :host => '0.0.0.0')
|
29
|
+
# # Use a Unix Domain Socket (which is more secure) instead:
|
30
|
+
# LiveConsole.new(:unix_socket, :path => '/tmp/my_liveconsole.sock',
|
31
|
+
# :mode => 0600, :uid => Process.uid, :gid => Process.gid)
|
32
|
+
# # By default, the mode is 0600, and the uid and gid are those of the
|
33
|
+
# # current process. These three options are for the file's permissions.
|
34
|
+
# # You can also supply a binding for IRB's toplevel:
|
35
|
+
# LiveConsole.new(:unix_socket,
|
36
|
+
# :path => "/tmp/live_console_#{Process.pid}.sock", :bind => binding)
|
26
37
|
#
|
27
|
-
# Creates a new LiveConsole. You must next call LiveConsole#
|
28
|
-
# want to spawn the thread to accept connections and
|
29
|
-
def initialize(
|
30
|
-
self.
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
# Creates a new LiveConsole. You must next call LiveConsole#start when you
|
39
|
+
# want to spawn the thread to accept connections and start the console.
|
40
|
+
def initialize(io_method, opts = {})
|
41
|
+
self.io_method = io_method.to_sym
|
42
|
+
self.bind = opts.delete :bind
|
43
|
+
unless IOMethods::List.include?(self.io_method)
|
44
|
+
raise ArgumentError, "Unknown IO method: #{io_method}"
|
45
|
+
end
|
46
|
+
|
47
|
+
init_io opts
|
48
|
+
end
|
49
|
+
|
50
|
+
# LiveConsole#start spawns a thread to listen for, accept, and provide an
|
51
|
+
# IRB console to new connections. If a thread is already running, this
|
52
|
+
# method simply returns false; otherwise, it returns the new thread.
|
53
|
+
def start
|
54
|
+
if thread
|
55
|
+
if thread.alive?
|
56
|
+
return false
|
57
|
+
else
|
58
|
+
thread.join
|
59
|
+
self.thread = nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
self.thread = Thread.new {
|
39
64
|
loop {
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
socket.close rescue nil
|
49
|
-
IO.select([tcp_server], [], [], 1)
|
50
|
-
|
51
|
-
retry
|
65
|
+
Thread.pass
|
66
|
+
if io.start
|
67
|
+
irb_io = GenericIOMethod.new io.raw_input, io.raw_output
|
68
|
+
begin
|
69
|
+
IRB.start_with_io(irb_io, bind)
|
70
|
+
rescue Errno::EPIPE => e
|
71
|
+
io.stop
|
72
|
+
end
|
52
73
|
end
|
53
74
|
}
|
54
75
|
}
|
55
|
-
|
76
|
+
thread
|
56
77
|
end
|
57
78
|
|
58
79
|
# Ends the running thread, if it exists. Returns true if a thread was
|
59
80
|
# running, false otherwise.
|
60
81
|
def stop
|
61
|
-
if
|
62
|
-
|
63
|
-
|
82
|
+
if thread
|
83
|
+
if thread == Thread.current
|
84
|
+
self.thread = nil
|
85
|
+
Thread.current.exit!
|
86
|
+
end
|
87
|
+
|
88
|
+
thread.exit
|
89
|
+
if thread.join(0.1).nil?
|
90
|
+
thread.exit!
|
91
|
+
end
|
92
|
+
self.thread = nil
|
64
93
|
true
|
65
94
|
else
|
66
95
|
false
|
67
96
|
end
|
68
97
|
end
|
69
98
|
|
99
|
+
# Restarts. Useful for binding changes. Return value is the same as for
|
100
|
+
# LiveConsole#start.
|
101
|
+
def restart
|
102
|
+
r = lambda { stop; start }
|
103
|
+
if thread == Thread.current # An odd case.
|
104
|
+
Thread.new &r
|
105
|
+
else
|
106
|
+
r.call
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
70
112
|
def init_irb
|
71
113
|
return if @@irb_inited_already
|
72
114
|
IRB.setup nil
|
73
115
|
@@irb_inited_already = true
|
74
116
|
end
|
117
|
+
|
118
|
+
def init_io opts
|
119
|
+
self.io = IOMethods.send(io_method).new opts
|
120
|
+
end
|
75
121
|
end
|
76
122
|
|
77
123
|
# We need to make a couple of changes to the IRB module to account for using a
|
@@ -80,7 +126,7 @@ module IRB
|
|
80
126
|
@inited = false
|
81
127
|
|
82
128
|
# Overridden a la FXIrb to accomodate our needs.
|
83
|
-
def IRB.start_with_io(io, &block)
|
129
|
+
def IRB.start_with_io(io, bind, &block)
|
84
130
|
unless @inited
|
85
131
|
setup '/dev/null'
|
86
132
|
IRB.parse_opts
|
@@ -88,7 +134,9 @@ module IRB
|
|
88
134
|
@inited = true
|
89
135
|
end
|
90
136
|
|
91
|
-
|
137
|
+
bind ||= IRB::Frame.top(1)
|
138
|
+
ws = IRB::WorkSpace.new(bind)
|
139
|
+
irb = Irb.new(ws, io, io)
|
92
140
|
|
93
141
|
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
|
94
142
|
@CONF[:MAIN_CONTEXT] = irb.context
|
@@ -102,7 +150,7 @@ module IRB
|
|
102
150
|
retry
|
103
151
|
end
|
104
152
|
}
|
105
|
-
print "\n"
|
153
|
+
irb.print "\n"
|
106
154
|
end
|
107
155
|
|
108
156
|
class Context
|
@@ -125,32 +173,52 @@ module IRB
|
|
125
173
|
end
|
126
174
|
end
|
127
175
|
|
128
|
-
# The
|
129
|
-
class
|
130
|
-
|
131
|
-
|
176
|
+
# The GenericIOMethod is a class that wraps I/O for IRB.
|
177
|
+
class GenericIOMethod < IRB::StdioInputMethod
|
178
|
+
# call-seq:
|
179
|
+
# GenericIOMethod.new io
|
180
|
+
# GenericIOMethod.new input, output
|
181
|
+
#
|
182
|
+
# Creates a GenericIOMethod, using either a single object for both input
|
183
|
+
# and output, or one object for input and another for output.
|
184
|
+
def initialize(input, output = nil)
|
185
|
+
@input, @output = input, output
|
132
186
|
@line = []
|
133
187
|
@line_no = 0
|
134
188
|
end
|
135
189
|
|
190
|
+
attr_reader :input
|
191
|
+
def output
|
192
|
+
@output || input
|
193
|
+
end
|
194
|
+
|
136
195
|
def gets
|
137
|
-
|
138
|
-
|
139
|
-
@line[@line_no += 1] =
|
140
|
-
@
|
196
|
+
output.print @prompt
|
197
|
+
output.flush
|
198
|
+
@line[@line_no += 1] = input.gets
|
199
|
+
# @io.flush # Not sure this is needed.
|
141
200
|
@line[@line_no]
|
142
201
|
end
|
143
202
|
|
144
|
-
#
|
145
|
-
|
146
|
-
|
147
|
-
|
203
|
+
# Returns the user input history.
|
204
|
+
def lines
|
205
|
+
@line.dup
|
206
|
+
end
|
148
207
|
|
149
208
|
def print(*a)
|
150
|
-
|
209
|
+
output.print *a
|
151
210
|
end
|
152
211
|
|
153
212
|
def file_name
|
154
|
-
|
213
|
+
input.inspect
|
214
|
+
end
|
215
|
+
|
216
|
+
def eof?
|
217
|
+
input.eof?
|
218
|
+
end
|
219
|
+
|
220
|
+
def close
|
221
|
+
input.close
|
222
|
+
output.close if @output
|
155
223
|
end
|
156
224
|
end
|
data/lib/live_console_config.rb
CHANGED
metadata
CHANGED
@@ -1,52 +1,67 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: live_console
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-10-19 00:00:00 -07:00
|
8
|
-
summary: A library to support adding a console to your running application.
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: pete.elmore@gmail.com
|
12
|
-
homepage: http://debu.gs/live-console
|
13
|
-
rubyforge_project:
|
14
|
-
description:
|
15
|
-
autorequire: live_console
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
4
|
+
version: 0.2.0
|
25
5
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
6
|
authors:
|
30
7
|
- Pete Elmore
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
- doc/lc_example.rb
|
35
|
-
- lib/live_console.rb
|
36
|
-
- lib/live_console_config.rb
|
37
|
-
test_files: []
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
38
11
|
|
39
|
-
|
12
|
+
date: 2008-10-15 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: pete.elmore@gmail.com
|
18
|
+
executables:
|
19
|
+
- udscat
|
20
|
+
extensions: []
|
40
21
|
|
41
22
|
extra_rdoc_files:
|
42
|
-
- doc/
|
23
|
+
- doc/lc_example.rb
|
43
24
|
- doc/LICENSE
|
25
|
+
- doc/README
|
26
|
+
- doc/lc_unix_example.rb
|
27
|
+
files:
|
28
|
+
- bin/udscat
|
44
29
|
- doc/lc_example.rb
|
45
|
-
|
46
|
-
|
47
|
-
|
30
|
+
- doc/LICENSE
|
31
|
+
- doc/README
|
32
|
+
- doc/lc_unix_example.rb
|
33
|
+
- lib/live_console_config.rb
|
34
|
+
- lib/live_console.rb
|
35
|
+
- lib/live_console
|
36
|
+
- lib/live_console/io_methods
|
37
|
+
- lib/live_console/io_methods/socket_io.rb
|
38
|
+
- lib/live_console/io_methods/unix_socket_io.rb
|
39
|
+
- lib/live_console/io_methods.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://debu.gs/live-console
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
48
44
|
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
49
59
|
requirements: []
|
50
60
|
|
51
|
-
|
61
|
+
rubyforge_project: live-console
|
62
|
+
rubygems_version: 1.3.0
|
63
|
+
signing_key:
|
64
|
+
specification_version: 2
|
65
|
+
summary: A library to support adding a console to your running application.
|
66
|
+
test_files: []
|
52
67
|
|