rogerdpack-live_console 0.2.2.3 → 0.2.3
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/Rakefile +90 -0
- data/doc/LICENSE +19 -0
- data/doc/README +137 -0
- data/doc/lc_example.rb +38 -0
- data/doc/lc_unix_example.rb +37 -0
- data/lib/live_console/io_methods/socket_io.rb +34 -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 +228 -0
- data/lib/live_console_config.rb +9 -0
- metadata +27 -14
data/Rakefile
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'rake/gempackagetask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'lib/live_console_config'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
$: << "#{File.dirname(__FILE__)}/lib"
|
7
|
+
|
8
|
+
spec = Gem::Specification.new { |s|
|
9
|
+
s.name = LiveConsoleConfig::PkgName
|
10
|
+
s.version = LiveConsoleConfig::Version
|
11
|
+
s.author = LiveConsoleConfig::Authors
|
12
|
+
s.email = LiveConsoleConfig::Email
|
13
|
+
s.homepage = LiveConsoleConfig::URL
|
14
|
+
s.rubyforge_project = LiveConsoleConfig::Project
|
15
|
+
|
16
|
+
s.platform = Gem::Platform::RUBY
|
17
|
+
|
18
|
+
s.files = Dir["{lib,doc,bin,ext}/**/*"].delete_if {|f|
|
19
|
+
/\/rdoc(\/|$)/i.match f
|
20
|
+
} + %w(Rakefile)
|
21
|
+
s.require_path = 'lib'
|
22
|
+
s.has_rdoc = true
|
23
|
+
s.extra_rdoc_files = Dir['doc/*'].select(&File.method(:file?))
|
24
|
+
s.extensions << 'ext/extconf.rb' if File.exist? 'ext/extconf.rb'
|
25
|
+
Dir['bin/*'].map(&File.method(:basename)).map(&s.executables.method(:<<))
|
26
|
+
|
27
|
+
s.summary = 'A library to support adding an irb console to your ' \
|
28
|
+
'running application.'
|
29
|
+
%w().each &s.method(:add_dependency)
|
30
|
+
}
|
31
|
+
|
32
|
+
Rake::RDocTask.new(:doc) { |t|
|
33
|
+
t.main = 'doc/README'
|
34
|
+
t.rdoc_files.include 'lib/**/*.rb', 'doc/*', 'bin/*', 'ext/**/*.c',
|
35
|
+
'ext/**/*.rb'
|
36
|
+
t.options << '-S' << '-N'
|
37
|
+
t.rdoc_dir = 'doc/rdoc'
|
38
|
+
}
|
39
|
+
|
40
|
+
Rake::GemPackageTask.new(spec) { |pkg|
|
41
|
+
pkg.need_tar_bz2 = true
|
42
|
+
}
|
43
|
+
|
44
|
+
desc "Builds and installs the gem for #{spec.name}"
|
45
|
+
task(:install => :package) {
|
46
|
+
g = "pkg/#{spec.name}-#{spec.version}.gem"
|
47
|
+
system "sudo gem install -l #{g}"
|
48
|
+
}
|
49
|
+
|
50
|
+
desc "Runs IRB, automatically require()ing #{spec.name}."
|
51
|
+
task(:irb) {
|
52
|
+
exec "irb -Ilib -r#{spec.name}"
|
53
|
+
}
|
54
|
+
|
55
|
+
desc "Cleans up the pkg directory."
|
56
|
+
task(:clean) {
|
57
|
+
FileUtils.rm_rf 'pkg'
|
58
|
+
}
|
59
|
+
|
60
|
+
desc "Generates a static gemspec file; useful for github."
|
61
|
+
task(:static_gemspec) {
|
62
|
+
# This whole thing is hacky.
|
63
|
+
spec.validate
|
64
|
+
spec_attrs = %w(
|
65
|
+
platform author email files require_path has_rdoc extra_rdoc_files
|
66
|
+
extensions executables name summary homepage
|
67
|
+
).map { |attr|
|
68
|
+
"\ts.#{attr} = #{spec.send(attr).inspect}\n"
|
69
|
+
}.join <<
|
70
|
+
"\ts.version = #{spec.version.to_s.inspect}\n" <<
|
71
|
+
spec.dependencies.map { |dep|
|
72
|
+
"\ts.add_dependency #{dep.inspect}\n"
|
73
|
+
}.join
|
74
|
+
|
75
|
+
File.open("#{spec.name}.gemspec", 'w') { |f|
|
76
|
+
f.print <<-EOGEMSPEC
|
77
|
+
# This is a static gempsec automatically generated by rake. It's better to
|
78
|
+
# edit the Rakefile than this file. It is kept in the repository for the
|
79
|
+
# benefit of github.
|
80
|
+
|
81
|
+
spec = Gem::Specification.new { |s|
|
82
|
+
#{spec_attrs}}
|
83
|
+
if __FILE__ == $0
|
84
|
+
Gem::Builder.new(spec).build
|
85
|
+
else
|
86
|
+
spec # Github wants this file to return the spec.
|
87
|
+
end
|
88
|
+
EOGEMSPEC
|
89
|
+
}
|
90
|
+
}
|
data/doc/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2007 Peter Elmore (pete.elmore at gmail.com)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
4
|
+
copy of this software and associated documentation files (the "Software"),
|
5
|
+
to deal in the Software without restriction, including without limitation
|
6
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
8
|
+
Software is furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
16
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
19
|
+
DEALINGS IN THE SOFTWARE.
|
data/doc/README
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
= LiveConsole
|
2
|
+
|
3
|
+
== Summary
|
4
|
+
|
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.
|
8
|
+
For example, you can:
|
9
|
+
* Inspect the state of a running application
|
10
|
+
* Change the state of the application
|
11
|
+
* Patch code on the fly, without a restart.
|
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.
|
15
|
+
|
16
|
+
== Stern Security Warning. Grrr.
|
17
|
+
|
18
|
+
Have a look at the bugs section. It should be pretty apparent that incorrect
|
19
|
+
use of this library could create a large security hole, especially before
|
20
|
+
authentication is implemented.
|
21
|
+
|
22
|
+
== Installation
|
23
|
+
|
24
|
+
You can install via rubygems,
|
25
|
+
|
26
|
+
gem install live_console
|
27
|
+
|
28
|
+
or plain old setup.rb:
|
29
|
+
|
30
|
+
ruby setup.rb install
|
31
|
+
|
32
|
+
== How to use LiveConsole
|
33
|
+
|
34
|
+
LiveConsole is very easy to use in your own app:
|
35
|
+
|
36
|
+
require 'rubygems'
|
37
|
+
require 'live_console'
|
38
|
+
|
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
|
43
|
+
# At this point, users can connect and get an IRB prompt.
|
44
|
+
lc.stop # Kills the LiveConsole thread
|
45
|
+
# Now, no one can connect.
|
46
|
+
|
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
|
+
|
65
|
+
Try just running it:
|
66
|
+
|
67
|
+
$ ruby doc/lc_example.rb 4000 test
|
68
|
+
# Then, in a different shell:
|
69
|
+
$ netcat localhost 4000
|
70
|
+
irb(main):001:0> puts 'Wow, magic!'
|
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
|
+
|
77
|
+
You can get creative about it, only starting LiveConsole when there's an
|
78
|
+
unhandled exception in your server, and then calling LiveConsole#stop when
|
79
|
+
you've diagnosed and fixed whatever the problem was.
|
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
|
+
|
94
|
+
== Bugs
|
95
|
+
|
96
|
+
LiveConsole lacks many of the niceties of IRB on the console, like Readline
|
97
|
+
support. For this, you can actually use the wonderful rlwrap program, which
|
98
|
+
wraps an arbitrary interactive program in readline. For example, to connect to
|
99
|
+
a LiveConsole on localhost:3333, use
|
100
|
+
rlwrap netcat localhost 3333
|
101
|
+
rlwrap is available with most Linux distributions or at
|
102
|
+
http://utopia.knoware.nl/~hlub/uck/rlwrap/ . It is seriously an incredibly
|
103
|
+
useful piece of software.
|
104
|
+
|
105
|
+
|
106
|
+
Typing exit, hitting ^D, or sending signals (like INT or STOP) doesn't work.
|
107
|
+
Just exit the program you used to connect to it. This has more to do with the
|
108
|
+
program you use to connect to the socket.
|
109
|
+
|
110
|
+
For TCP connections, there is no authentication support yet, although it is
|
111
|
+
planned for the near future. This creates a security risk: anyone that can
|
112
|
+
connect to the socket can run arbitrary Ruby code as the user who owns the
|
113
|
+
process. In fact, even binding to localhost can be a security issue if you're
|
114
|
+
on a box with any untrusted users. If there's a chance you don't know what
|
115
|
+
you're doing, avoid using this library. The Unix Domain Socket version is more
|
116
|
+
secure, as you can control access via filesystem permissions.
|
117
|
+
|
118
|
+
Only one client can connect at a time. I don't think anyone needs multiple LC
|
119
|
+
connections to serve multiple instances of IRB to various clients, but if you
|
120
|
+
need it, let me know.
|
121
|
+
|
122
|
+
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.
|
123
|
+
|
124
|
+
Other than that, LiveConsole doesn't have any known bugs, but it is odd
|
125
|
+
software that also monkey-patches IRB, so they are likely to be there. Bug
|
126
|
+
reports and patches gratefully accepted.
|
127
|
+
|
128
|
+
== Credits
|
129
|
+
|
130
|
+
Pete Elmore, author -- (pete.elmore(a)gmail.com)
|
131
|
+
|
132
|
+
Roger D. Pack (http://betterlogic.com/roger/) provided patches and Windows
|
133
|
+
support
|
134
|
+
|
135
|
+
== Home page
|
136
|
+
|
137
|
+
http://debu.gs/live-console
|
data/doc/lc_example.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'live_console' # load from gem
|
5
|
+
|
6
|
+
print <<-EOF
|
7
|
+
This is a demo program for LiveConsole. It starts a LiveConsole on the
|
8
|
+
specified port, and you can connect to it by using netcat or telnet to connect
|
9
|
+
to the specified port.
|
10
|
+
Usage:
|
11
|
+
#{$0} [port_number [value_for_$x]]
|
12
|
+
The default port is 3333, and $x is set by default to nil. Run this program,
|
13
|
+
and then in a different terminal, connect to it via netcat or telnet. You can
|
14
|
+
check that the value of $x is exactly what you set it to, and that you're
|
15
|
+
working inside this process, but there's not much to do inside the example
|
16
|
+
script. :)
|
17
|
+
|
18
|
+
EOF
|
19
|
+
|
20
|
+
port = ARGV.first.to_i
|
21
|
+
port = port.zero? ? 3333 : port
|
22
|
+
$x = ARGV[1]
|
23
|
+
|
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}"
|
29
|
+
|
30
|
+
oldx = $x
|
31
|
+
loop {
|
32
|
+
if $x != oldx
|
33
|
+
puts "The time is now #{Time.now.strftime('%R:%S')}.",
|
34
|
+
"The value of $x changed from #{oldx.inspect} to #{$x.inspect}."
|
35
|
+
oldx = $x
|
36
|
+
end
|
37
|
+
sleep 1
|
38
|
+
}
|
@@ -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,34 @@
|
|
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
|
+
|
11
|
+
begin
|
12
|
+
@server ||= TCPServer.new host, port
|
13
|
+
rescue => e
|
14
|
+
puts "unable to start live console server #{e}"
|
15
|
+
raise e
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
IO.select([server])
|
20
|
+
self.raw_input = self.raw_output = server.accept
|
21
|
+
return true
|
22
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
|
23
|
+
Errno::EINTR => e
|
24
|
+
select
|
25
|
+
retry
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def stop
|
30
|
+
select
|
31
|
+
raw_input.close rescue nil
|
32
|
+
end
|
33
|
+
|
34
|
+
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
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
# LiveConsole
|
2
|
+
# Pete Elmore (pete.elmore@gmail.com), 2007-10-18
|
3
|
+
# debu.gs/live-console
|
4
|
+
# See doc/LICENSE.
|
5
|
+
|
6
|
+
require 'irb'
|
7
|
+
require 'irb/frame'
|
8
|
+
require 'socket'
|
9
|
+
|
10
|
+
# LiveConsole provides a socket that can be connected to via netcat or telnet
|
11
|
+
# to use to connect to an IRB session inside a running process. It creates a
|
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.
|
16
|
+
class LiveConsole
|
17
|
+
include Socket::Constants
|
18
|
+
autoload :IOMethods, 'live_console/io_methods'
|
19
|
+
|
20
|
+
attr_accessor :io_method, :io, :thread, :bind
|
21
|
+
private :io_method=, :io=, :thread=
|
22
|
+
|
23
|
+
# call-seq:
|
24
|
+
# # Bind a LiveConsole to localhost:3030 (only allow clients on this
|
25
|
+
# # machine to connect):
|
26
|
+
# LiveConsole.new :socket, :port => 3030
|
27
|
+
# # Accept connections from anywhere on port 3030. Ridiculously insecure:
|
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)
|
37
|
+
#
|
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 {
|
64
|
+
loop {
|
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
|
73
|
+
io.stop
|
74
|
+
end
|
75
|
+
}
|
76
|
+
}
|
77
|
+
thread
|
78
|
+
end
|
79
|
+
|
80
|
+
# Ends the running thread, if it exists. Returns true if a thread was
|
81
|
+
# running, false otherwise.
|
82
|
+
def stop
|
83
|
+
if thread
|
84
|
+
if thread == Thread.current
|
85
|
+
self.thread = nil
|
86
|
+
Thread.current.exit!
|
87
|
+
end
|
88
|
+
|
89
|
+
thread.exit
|
90
|
+
if thread.join(0.1).nil?
|
91
|
+
thread.exit!
|
92
|
+
end
|
93
|
+
self.thread = nil
|
94
|
+
true
|
95
|
+
else
|
96
|
+
false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Restarts. Useful for binding changes. Return value is the same as for
|
101
|
+
# LiveConsole#start.
|
102
|
+
def restart
|
103
|
+
r = lambda { stop; start }
|
104
|
+
if thread == Thread.current
|
105
|
+
Thread.new &r # Leaks a thread, but works.
|
106
|
+
else
|
107
|
+
r.call
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def init_irb
|
114
|
+
return if @@irb_inited_already
|
115
|
+
IRB.setup nil
|
116
|
+
@@irb_inited_already = true
|
117
|
+
end
|
118
|
+
|
119
|
+
def init_io opts
|
120
|
+
self.io = IOMethods.send(io_method).new opts
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# We need to make a couple of changes to the IRB module to account for using a
|
125
|
+
# weird I/O method and re-starting IRB from time to time.
|
126
|
+
module IRB
|
127
|
+
@inited = false
|
128
|
+
|
129
|
+
ARGV = []
|
130
|
+
|
131
|
+
# Overridden a la FXIrb to accomodate our needs.
|
132
|
+
def IRB.start_with_io(io, bind, &block)
|
133
|
+
unless @inited
|
134
|
+
setup '/dev/null'
|
135
|
+
IRB.parse_opts
|
136
|
+
IRB.load_modules
|
137
|
+
@inited = true
|
138
|
+
end
|
139
|
+
|
140
|
+
ws = IRB::WorkSpace.new(bind)
|
141
|
+
irb = Irb.new(ws, io, io)
|
142
|
+
bind ||= IRB::Frame.top(1) rescue TOPLEVEL_BINDING
|
143
|
+
|
144
|
+
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
|
145
|
+
@CONF[:MAIN_CONTEXT] = irb.context
|
146
|
+
@CONF[:PROMPT_MODE] = :INF_RUBY
|
147
|
+
|
148
|
+
catch(:IRB_EXIT) {
|
149
|
+
begin
|
150
|
+
irb.eval_input
|
151
|
+
rescue StandardError => e
|
152
|
+
irb.print([e.to_s, e.backtrace].flatten.join("\n") + "\n")
|
153
|
+
retry
|
154
|
+
end
|
155
|
+
}
|
156
|
+
irb.print "\n"
|
157
|
+
end
|
158
|
+
|
159
|
+
class Context
|
160
|
+
# Fix an IRB bug; it ignores your output method.
|
161
|
+
def output *args
|
162
|
+
@output_method.print *args
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Irb
|
167
|
+
# Fix an IRB bug; it ignores your output method.
|
168
|
+
def printf(*args)
|
169
|
+
context.output(sprintf(*args))
|
170
|
+
end
|
171
|
+
|
172
|
+
# Fix an IRB bug; it ignores your output method.
|
173
|
+
def print(*args)
|
174
|
+
context.output *args
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# The GenericIOMethod is a class that wraps I/O for IRB.
|
180
|
+
class GenericIOMethod < IRB::StdioInputMethod
|
181
|
+
# call-seq:
|
182
|
+
# GenericIOMethod.new io
|
183
|
+
# GenericIOMethod.new input, output
|
184
|
+
#
|
185
|
+
# Creates a GenericIOMethod, using either a single object for both input
|
186
|
+
# and output, or one object for input and another for output.
|
187
|
+
def initialize(input, output = nil)
|
188
|
+
@input, @output = input, output
|
189
|
+
@line = []
|
190
|
+
@line_no = 0
|
191
|
+
@stdin = input # fake it into thinking stdin is our input
|
192
|
+
end
|
193
|
+
|
194
|
+
attr_reader :input
|
195
|
+
def output
|
196
|
+
@output || input
|
197
|
+
end
|
198
|
+
|
199
|
+
def gets
|
200
|
+
output.print @prompt
|
201
|
+
output.flush
|
202
|
+
@line[@line_no += 1] = input.gets
|
203
|
+
# @io.flush # Not sure this is needed.
|
204
|
+
@line[@line_no]
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns the user input history.
|
208
|
+
def lines
|
209
|
+
@line.dup
|
210
|
+
end
|
211
|
+
|
212
|
+
def print(*a)
|
213
|
+
output.print *a
|
214
|
+
end
|
215
|
+
|
216
|
+
def file_name
|
217
|
+
input.inspect
|
218
|
+
end
|
219
|
+
|
220
|
+
def eof?
|
221
|
+
input.eof?
|
222
|
+
end
|
223
|
+
|
224
|
+
def close
|
225
|
+
input.close
|
226
|
+
output.close if @output
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# This module houses a pile of informative constants for LiveConsole.
|
2
|
+
module LiveConsoleConfig
|
3
|
+
Authors = 'Pete Elmore'
|
4
|
+
Email = 'pete.elmore@gmail.com'
|
5
|
+
PkgName = 'live_console'
|
6
|
+
Version = '0.2.3'
|
7
|
+
URL = 'http://debu.gs/live-console'
|
8
|
+
Project = 'live-console'
|
9
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rogerdpack-live_console
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pete Elmore
|
@@ -9,23 +9,36 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-16 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description:
|
17
|
-
email:
|
18
|
-
- pete.elmore@gmail.com
|
16
|
+
description:
|
17
|
+
email: pete.elmore@gmail.com
|
19
18
|
executables:
|
20
19
|
- udscat
|
21
20
|
extensions: []
|
22
21
|
|
23
|
-
extra_rdoc_files:
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
extra_rdoc_files:
|
23
|
+
- doc/LICENSE
|
24
|
+
- doc/README
|
25
|
+
- doc/lc_example.rb
|
26
|
+
- doc/lc_unix_example.rb
|
27
|
+
files:
|
28
|
+
- lib/live_console/io_methods/socket_io.rb
|
29
|
+
- lib/live_console/io_methods/unix_socket_io.rb
|
30
|
+
- lib/live_console/io_methods.rb
|
31
|
+
- lib/live_console.rb
|
32
|
+
- lib/live_console_config.rb
|
33
|
+
- doc/LICENSE
|
34
|
+
- doc/README
|
35
|
+
- doc/lc_example.rb
|
36
|
+
- doc/lc_unix_example.rb
|
37
|
+
- bin/udscat
|
38
|
+
- Rakefile
|
39
|
+
has_rdoc: true
|
28
40
|
homepage: http://debu.gs/live-console
|
41
|
+
licenses:
|
29
42
|
post_install_message:
|
30
43
|
rdoc_options: []
|
31
44
|
|
@@ -45,10 +58,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
45
58
|
version:
|
46
59
|
requirements: []
|
47
60
|
|
48
|
-
rubyforge_project:
|
49
|
-
rubygems_version: 1.
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.3.5
|
50
63
|
signing_key:
|
51
|
-
specification_version:
|
52
|
-
summary: A library to support adding
|
64
|
+
specification_version: 2
|
65
|
+
summary: A library to support adding an irb console to your running application.
|
53
66
|
test_files: []
|
54
67
|
|