Skip to content

Commit

Permalink
Finished custom Handlers support :)
Browse files Browse the repository at this point in the history
  • Loading branch information
Josep M. Bach committed Nov 21, 2010
1 parent 08ad4ae commit 1f06388
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
hijacker (0.0.2)
hijacker (0.1.0)
trollop

GEM
Expand Down
95 changes: 84 additions & 11 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
#hijacker

A little gem that hijacks any ruby object and broadcasts all its activity
to a particular hijacker server. Useful for logging and those awfully hardcore
debugging afternoons! There might be other uses to it, for sure. Just be
creative :)
to a particular hijacker server. The server exposes a special handler object
which then does fun things with the received data!

For example, by now there is only one handler: Logger. This handler shows the
received activity in the server output with fancy logging-style colors. Could
be quite useful for those awfully long debugging afternoons!

Of course, you can write your own handlers to do whatever you want with the
data: maybe record how many arguments do your methods accept on average, log
into disk any method calls containing a certain type of argument... just be
creative! :)

(See the "Extending hijacker blabla" part below to know how to write your own
handlers)

Hijacker is tested with Ruby 1.8.7, 1.9.2, JRuby 1.5.3 and Rubinius 1.1.

Expand All @@ -23,16 +34,24 @@ otherwise, as long as it's before hijacking any object):

And that's it! Oooor not. You have to spawn your server. In the command line:

hijacker
hijacker <handler>

Where <handler> must be a registered handler (for now there is only 'logger').
So you type:

And it will output the URI for this server. *Note this* and pass it to your
configuration block!
hijacker logger

And it will output the URI for this super fancy hijacker logging server.
*Remember this URI* and pass it to your configuration block!

Some options you can pass to the server command:

hijacker --without-timestamps (don't show the timestamps)
hijacker --without-classes (don't show the object classes)
hijacker --port 1234 (spawn the server in port 1234 rather than 8787)
hijacker <handler> --port 1234 (spawn the server in port 1234 rather than 8787)

Specific handlers can accept specific options. Logger accepts these:

hijacker logger --without-timestamps (don't show the timestamps)
hijacker logger --without-classes (don't show the object classes)

##Ok, and now for some hijacking action!

Expand All @@ -54,11 +73,13 @@ Some options you can pass to the server command:
instance = MyClass.new
instance.foo(3, 4)

Run this code and, if you look at the server output, you'll see nothing less than...
Run this code and, given we are using the Logger handler, if you look at the server output, you'll see nothing less than...

<a nice timestamp> MyClass (Class) received :new and returned #<MyClass:0x000000874> (MyClass)
<a nice timestamp> #<MyClass:0x000000874> (MyClass) received :foo with 3 (Fixnum), 4 (Fixnum) and returned 7 (Fixnum)

But in a nice set of colors.

If you want to un-hijack any object, just call #restore:

Hijacker.restore(MyClass)
Expand Down Expand Up @@ -95,7 +116,59 @@ Of course, you can specify a particular server uri for a block, with #spying:
# all the activity of foo_object inside this block
# will be sent to the hijacker server on druby://localhost:1234
end


##Extending hijacker with moar handlers

It is really easy to write your own handlers. Why don't you write one and send
me a pull request? I mean now. What are you waiting for, why are you still reading?

Ok, maybe a bit of explanation on that. Handlers live here:

lib/hijacker/handlers/your_handler.rb

They are autoloaded and automatically registered, so all you have to do is
write them like this:


module Hijacker
class MyHandler < Handler # Yes, you have to subclass Hijacker::Handler!

# You must implement a class method named cli_options which must
# return a Trollop-friendly Proc, for command-line options parsing.
#
# These options can be accessed within the #handle method by calling
# the opts method.
#
def self.cli_options
Proc.new {
opt :without_foo, "Don't use foo to handle the method name"
opt :using_bar, "Use bar as much as you can"
}
end

# This is the most important method. This is what is called every time
# a method call is performed on a hijacked object. The received params
# look like this:
#
# method :foo
#
# args [{:inspect => '3', :class => 'Fixnum'},
# {:inspect => '"string"', :class => 'String'}]
#
# retval [{:inspect => ':bar', :class => 'Symbol'}]
#
# object [{:inspect => '#<MyClass:0x003457>', :class => 'MyClass'}]
#
def handle(method, args, retval, object)
# Do what you want with these!
end

end
end

Try to think of creative uses of hijacker, write your own handlers and send
them to me ZOMG I CAN HAZ MOAR HENDLARZ

##Note on Patches/Pull Requests

* Fork the project.
Expand Down
33 changes: 26 additions & 7 deletions bin/hijacker
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,32 @@
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'

require 'hijacker'
require 'hijacker/version'

SUB_COMMANDS = Hijacker::Handler.handlers
opts = Trollop::options do
version "hijacker 0.0.1 (c) 2010 Josep M. Bach"
version "hijacker #{Hijacker::VERSION} (c) 2010 Josep M. Bach"
banner <<-EOS
Hijacker server listens for reports by hijackers spying on ruby objects.
Hijacker server listens for reports by hijackers spying on ruby objects,
and passes them to a given handler to process those reports.
Specific handlers may accept specific options. To learn about which options
does a particular handler accept, try:
hijacker <handler> --help
The available handlers are:
#{SUB_COMMANDS.join(', ')}
Usage:
hijacker [options]
hijacker <handler> [options]
where [options] are:
EOS
opt :without_classes, "Don't show classes of objects"
opt :without_timestamps, "Don't show timestamps"
opt :port, "DRb port to use (default is 8787)", :default => 8787
stop_on SUB_COMMANDS
end

# Port resolution
begin
raise unless opts[:port].to_i > 0 && opts[:port].to_i < 9999
rescue
Expand All @@ -26,15 +37,23 @@ end

DRB_URI="druby://localhost:#{opts[:port]}"

# Handler resolution
handler = ARGV.shift # get the handler
Trollop::die "You need to specify a handler, which must be one of the following: #{SUB_COMMANDS.join(', ')}\n\nMaybe you just feel a bit lost.." unless SUB_COMMANDS.include?(handler)

handler_class = eval("Hijacker::#{handler.capitalize}")
Trollop::options(&handler_class.cli_options)

# Start up the DRb service
DRb.start_service DRB_URI, Hijacker::Logger.new(opts)
DRb.start_service DRB_URI, handler_class.new(opts)

ANSI = Hijacker::Logger::ANSI
# We need the uri of the service to connect a client
welcome = []
welcome << ANSI[:BOLD] + "hijacker server"
welcome << "listening on"
welcome << ANSI[:BOLD] + DRb.uri + ANSI[:RESET]
welcome << ANSI[:BOLD] + DRb.uri
welcome << "\nUsing " + ANSI[:GREEN] + handler.capitalize + ANSI[:RESET] + " handler" + ANSI[:RESET]
puts welcome.join("#{ANSI[:RESET]} ") + "\n"

# We need the uri of the service to connect a client
Expand Down
28 changes: 26 additions & 2 deletions lib/hijacker/handler.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
module Hijacker
class Handler
# Your custom handlers need to respond to this class method, which will be
# expected to return a proc meant to be sent to Trollop during the command
# line parsing. For example, the Logger handler implements cli_options like
# this:
#
# def self.cli_options
# Proc.new {
# opt :without_classes, "Don't show classes of objects"
# opt :without_timestamps, "Don't show timestamps"
# }
# end

# Make dRuby send Handler instances as dRuby references,
# not copies.
include DRb::DRbUndumped

attr_reader :opts

def initialize(opts)
@opts = opts
end
Expand All @@ -25,10 +36,23 @@ def handle(method, args, retval, object)
raise NotImplementedError.new("You are supposed to subclass Handler")
end

class << self

@@handlers = []

def register_handler(handler)
handler.match(/handlers\/(\w+)/)
handler = $1.strip if $1
@@handlers << handler
end
def handlers
@@handlers
end
end
end
end

# Automatically load all handlers
Dir[File.dirname(File.join(File.dirname(__FILE__), 'handlers', '**', '*.rb'))].entries.each do |handler|
require handler
require(handler) && Hijacker::Handler.register_handler(handler)
end
7 changes: 7 additions & 0 deletions lib/hijacker/handlers/logger.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
module Hijacker
class Logger < Handler

def self.cli_options
Proc.new {
opt :without_classes, "Don't show classes of objects"
opt :without_timestamps, "Don't show timestamps"
}
end

ANSI = {:RESET=>"\e[0m", :BOLD=>"\e[1m", :UNDERLINE=>"\e[4m",
:LGRAY=>"\e[0;37m", :GRAY=>"\e[1;30m",
:RED=>"\e[31m",
Expand Down
2 changes: 1 addition & 1 deletion lib/hijacker/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Hijacker
VERSION = "0.0.2"
VERSION = "0.1.0"
end
11 changes: 11 additions & 0 deletions spec/hijacker/handler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ module Hijacker
}.to raise_error NotImplementedError
end
end

describe "class methods" do

describe "#register_handler" do
it 'registers a loaded handler' do
Hijacker::Handler.register_handler "/path/to/my/handlers/benchmark.rb"
Hijacker::Handler.handlers.should include('benchmark')
end
end

end

end
end
5 changes: 5 additions & 0 deletions spec/hijacker/handlers/logger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ module Hijacker
subject.should be_kind_of(Handler)
end

it "implements the cli_options class method" do
Hijacker::Logger.should respond_to(:cli_options)
Hijacker::Logger.cli_options.should be_kind_of(Proc)
end

describe "#handle" do

let(:args) do
Expand Down

0 comments on commit 1f06388

Please sign in to comment.