Permalink
Browse files

Initial check-in.

  • Loading branch information...
0 parents commit 72437fb9dddf94ae398705726ab9d1437e5d46bb tailor committed Oct 19, 2007
Showing with 1,900 additions and 0 deletions.
  1. +46 −0 Rakefile
  2. +19 −0 doc/LICENSE
  3. +34 −0 doc/README
  4. +26 −0 doc/waves_lc
  5. +26 −0 gemspec
  6. +156 −0 lib/live_console.rb
  7. +8 −0 lib/live_console_config.rb
  8. +1,585 −0 setup.rb
@@ -0,0 +1,46 @@
+require 'rake'
+require 'rake/testtask'
+
+require 'fileutils'
+
+require 'lib/live_console_config'
+
+def dinstall(dir, verbose = false)
+ ['bin', 'lib'].each { |f|
+ install f, dir, verbose
+ }
+end
+
+$distname = "#{LiveConsoleConfig::PkgName}-#{LiveConsoleConfig::Version}"
+$tgz = "#{$distname}.tar.gz"
+$tarbz2 = "#{$distname}.tar.bz2"
+$gem = "#{$distname}.gem"
+$exclude = %W(
+ --exclude=#{$distname}/#{$distname}
+ --exclude=distrib
+ --exclude=tags
+ --exclude=rdoc
+ --exclude=.*.swp
+ --exclude=.svn
+ --exclude=.config
+ --exclude=Rakefile
+).join(' ')
+
+task :default => :packages
+
+task(:packages) {
+ FileUtils.mkdir_p 'distrib'
+ system "ruby gemspec"
+ system "mv #{$distname}.gem distrib"
+
+ system "ln -sf . #{$distname}"
+
+ system "tar czhf distrib/#{$tgz} #{$distname} #{$exclude}"
+ system "tar cjhf distrib/#{$tarbz2} #{$distname} #{$exclude}"
+
+ File.unlink "#{$distname}"
+}
+
+task(:install) {
+ system "ruby setup.rb install"
+}
@@ -0,0 +1,19 @@
+Copyright (c) 2007 Peter Elmore (pete.elmore at gmail.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,34 @@
+= LiveConsole
+
+== Installation
+
+You can install via rubygems,
+
+ gem install live_console
+
+or plain old setup.rb:
+
+ ruby setup.rb install
+
+== How to use LiveConsole
+
+LiveConsole is very easy to use in your own app. It provides an IRB shell for
+
+== Bugs
+
+LiveConsole lacks many of the niceties of IRB on the console, like Readline
+support.
+
+Using exit, EOF, or sending signals (like INT or STOP) don't work. Just exit
+the program you used to connect to it.
+
+Other than that, LiveConsole doesn't have any known bugs, but it is alpha
+software, so they are likely to be there.
+
+== Credits
+
+Pete Elmore -- (pete.elmore(a)gmail.com)
+
+== Home page
+
+http://debu.gs/live-console
@@ -0,0 +1,26 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'live_console'
+
+print <<-EOF
+This is a demo program for LiveConsole. It starts a LiveConsole on the
+specified port, and you can connect to it by using netcat or telnet to connect
+to the specified port.
+ Usage:
+ #{$0} [port_number [value_for_$x]]
+The default port is 3333, and $x is set by default to nil. Run this program,
+and then in a different terminal, connect to it via netcat or telnet. You can
+check that the value of $x is exactly what you set it to, and that you're
+working inside this process, but there's not much to do inside the example
+script. :)
+EOF
+
+port = ARGV.first.to_i
+port = port.zero? ? 3333 : port
+$x = ARGV[1]
+
+lc = LiveConsole.new port
+lc.run
+
+loop { puts "I'm still alive. (#{Time.now})"; sleep 10 }
@@ -0,0 +1,26 @@
+require 'rubygems'
+require 'lib/live_console_config'
+
+SPEC = Gem::Specification.new { |s|
+ s.name = LiveConsoleConfig::PkgName
+ s.version = LiveConsoleConfig::Version
+ s.author = LiveConsoleConfig::Authors
+ s.email = LiveConsoleConfig::Email
+ s.homepage = LiveConsoleConfig::URL
+ s.platform = Gem::Platform::RUBY
+ s.summary =
+ 'A library to support adding a console to your running application.'
+ s.files = Dir.glob("{bin,doc,lib}/**/*").delete_if { |file|
+ [ /\/rdoc\//i, # No rdoc
+ ].find { |rx| rx.match file }
+ }
+ s.require_path 'lib'
+ s.autorequire = 'live_console'
+ s.has_rdoc = true
+ s.extra_rdoc_files = %w(doc/README doc/LICENSE)
+}
+
+if __FILE__ == $0
+ Gem::manage_gems
+ Gem::Builder.new(SPEC).build
+end
@@ -0,0 +1,156 @@
+# LiveConsole
+# Pete Elmore (pete.elmore@gmail.com), 2007-10-18
+# debu.gs/live-console
+# See doc/LICENSE.
+
+require 'irb'
+require 'socket'
+
+# LiveConsole provides a socket that can be connected to via netcat or telnet
+# to use to connect to an IRB session inside a running process. It creates a
+# thread that listens on the specified address/port, and presents connecting
+# clients with an IRB shell. Using this, you can execute code on a running
+# instance of a Ruby process to inspect the state or even patch code on the
+# fly. There is currently no readline support.
+class LiveConsole
+ include Socket::Constants
+
+ attr_accessor :tcp_server, :lc_thread
+ private :tcp_server=, :lc_thread=
+
+ # call-seq:
+ # # Bind a LiveConsole to localhost:3030:
+ # LiveConsole.new 3030
+ # # Accept connections from anywhere on port 3030. Ridiculously insecure:
+ # LiveConsole.new(3030, 'Your.IP.address')
+ #
+ # Creates a new LiveConsole. You must next call LiveConsole#run when you
+ # want to spawn the thread to accept connections and run the console.
+ def initialize(listen_port, listen_addr = '127.0.0.1')
+ self.tcp_server = TCPServer.new listen_addr, listen_port
+ end
+
+ # LiveConsole#run spawns a thread to listen for, accept, and provide an IRB
+ # console to new connections. If a thread is already running, this method
+ # simply returns false; otherwise, it returns the new thread.
+ def run
+ return false if lc_thread
+ self.lc_thread = Thread.new {
+ loop {
+ socket = nil
+ begin
+ Thread.pass
+ socket = tcp_server.accept_nonblock
+ io = SocketIOMethod.new(socket)
+ IRB.start_with_io(io)
+ rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
+ Errno::EINTR
+ socket.close rescue nil
+ IO.select([tcp_server], [], [], 1)
+
+ retry
+ end
+ }
+ }
+ lc_thread
+ end
+
+ # Ends the running thread, if it exists. Returns true if a thread was
+ # running, false otherwise.
+ def stop
+ if lc_thread
+ lc_thread.exit
+ self.lc_thread = nil
+ true
+ else
+ false
+ end
+ end
+
+ def init_irb
+ return if @@irb_inited_already
+ IRB.setup nil
+ @@irb_inited_already = true
+ end
+end
+
+# We need to make a couple of changes to the IRB module to account for using a
+# weird I/O method and re-starting IRB from time to time.
+module IRB
+ @inited = false
+
+ # Overridden a la FXIrb to accomodate our needs.
+ def IRB.start_with_io(io, &block)
+ unless @inited
+ setup '/dev/null'
+ IRB.parse_opts
+ IRB.load_modules
+ @inited = true
+ end
+
+ irb = Irb.new(nil, io, io)
+
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
+ @CONF[:MAIN_CONTEXT] = irb.context
+ @CONF[:PROMPT_MODE] = :INF_RUBY
+
+ catch(:IRB_EXIT) {
+ begin
+ irb.eval_input
+ rescue StandardError => e
+ irb.print([e.to_s, e.backtrace].flatten.join("\n") + "\n")
+ retry
+ end
+ }
+ print "\n"
+ end
+
+ class Context
+ # Fix an IRB bug; it ignores your output method.
+ def output *args
+ @output_method.print *args
+ end
+ end
+
+ class Irb
+ # Fix an IRB bug; it ignores your output method.
+ def printf(*args)
+ context.output(sprintf(*args))
+ end
+
+ # Fix an IRB bug; it ignores your output method.
+ def print(*args)
+ context.output *args
+ end
+ end
+end
+
+# The SocketIOMethod is a class that wraps I/O over a socket for IRB.
+class SocketIOMethod < IRB::StdioInputMethod
+ def initialize(socket)
+ @socket = socket
+ @line = []
+ @line_no = 0
+ end
+
+ def gets
+ @socket.print @prompt
+ @socket.flush
+ @line[@line_no += 1] = @socket.gets
+ @socket.flush
+ @line[@line_no]
+ end
+
+ # These just pass through to the socket.
+ %w(eof? close).each { |mname|
+ define_method(mname) { || @socket.send mname }
+ }
+
+ def print(*a)
+ @socket.print *a
+ end
+
+ def file_name
+ @socket.inspect
+ end
+end
@@ -0,0 +1,8 @@
+# This module houses a pile of informative constants for LiveConsole.
+module LiveConsoleConfig
+ Authors = 'Pete Elmore'
+ Email = 'pete.elmore@gmail.com'
+ PkgName = 'live_console'
+ Version = '0.1.0'
+ URL = 'http://debu.gs/live-console'
+end
Oops, something went wrong.

0 comments on commit 72437fb

Please sign in to comment.