<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>test/test_legs.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -59,4 +59,4 @@ When you connect to this server, you can call the +count+ method. Each time you
 
 And if you run that script, you should see in your terminal: &quot;1, 2, 3&quot;. That's pretty much how Legs works. An instance of legs has a few of it's own special methods: &lt;tt&gt;close!&lt;/tt&gt;, &lt;tt&gt;notify!&lt;/tt&gt;, &lt;tt&gt;send!&lt;/tt&gt;, &lt;tt&gt;send_async!&lt;/tt&gt;, &lt;tt&gt;connected?&lt;/tt&gt;, +socket+, +parent+, +meta+, and &lt;tt&gt;send_data!&lt;/tt&gt;. If you need to actually run a method on your server with one of these names, do: &lt;tt&gt;server.send!(:connected?, a_param, another_param)&lt;/tt&gt; or whatever, and it'll run that method on the server for you. If you want to let the server know something, but don't care about the method's response, or any errors, you can do the same thing with &lt;tt&gt;legs.notify!&lt;/tt&gt;, which will make your program run faster, especially if it's running through the web. :)
 
-Finally, if you're making a program for running over the internet, and want to make your app more responsive, you can call methods asyncronously. What this means is that the method won't return immidiately, but instead will send the request out to your network, and your program will continue to run, and then when the server responds, a block you provide will be run. The block is passed an object, call that object's +result+ or +value+ method to get the server response, or it will raise an error if the server had an error, so you can use it as though it were a syncronous response. Error is only raised the first time you call it
+Finally, if you're making a program for running over the internet, and want to make your app more responsive, you can call methods asyncronously. What this means is that the method won't return immidiately, but instead will send the request out to your network, and your program will continue to run, and then when the server responds, a block you provide will be run. The block is passed an object, call that object's +result+ or +value+ method to get the server response, or it will raise an error if the server had an error, so you can use it as though it were a syncronous response. Error is only raised the first time you call it. To do this, just give the thing a block like this: &lt;tt&gt;server.some_method { |result| ... do stuff with result.value ... }&lt;/tt&gt;.</diff>
      <filename>README.rdoc</filename>
    </modified>
    <modified>
      <diff>@@ -1,13 +1,11 @@
-# Legs take you places, a networking companion to Shoes
-require 'rubygems'
+# Legs take you places, a networking companion
+['rubygems', 'socket', 'thread'].each { |i| require i }
 require 'json' unless self.class.const_defined? 'JSON'
-require 'socket'
-require 'thread'
-
-Thread.abort_on_exception = true # Should be able to run without this, hopefully. Helps with debugging though
 
 class Legs
+  # general getters
   attr_reader :socket, :parent, :meta
+  def inspect; &quot;&lt;Legs:#{object_id} Meta: #{@meta.inspect}&gt;&quot;; end
   
   # Legs.new for a client, subclass to make a server, .new then makes server and client!
   def initialize(host = 'localhost', port = 30274)
@@ -28,7 +26,7 @@ class Legs
     
     
     @handle_data = Proc.new do |data|
-      data = self.__json_restore(JSON.parse(data))
+      data = json_restore(JSON.parse(data))
       
       if data['method']
         (@parent || self.class).__data!(data, self)
@@ -42,34 +40,32 @@ class Legs
     @thread = Thread.new do
       until @socket.closed?
         begin
-          self.close! if @socket.eof?
+          close! if @socket.eof?
           data = nil
-          @socket_mutex.synchronize { data = @socket.gets(self.class.terminator) }
+          @socket_mutex.synchronize { data = @socket.gets(self.class.terminator) rescue nil }
           if data.nil?
-            self.close!
+            close!
           else
             @handle_data[data]
           end
         rescue JSON::ParserError =&gt; e
-          self.send_data!({&quot;error&quot; =&gt; &quot;JSON provided is invalid. See http://json.org/ to see how to format correctly.&quot;})
+          send_data!({&quot;error&quot; =&gt; &quot;JSON provided is invalid. See http://json.org/ to see how to format correctly.&quot;})
         rescue IOError =&gt; e
-          self.close!
+          close!
         end
       end
     end
   end
   
   # I think you can guess this one
-  def connected?
-    self.class.connections.include? self
-  end
+  def connected?; self.class.connections.include?(self); end
   
   # closes the connection and the threads and stuff for this user
   def close!
     return if @disconnected == true
     
     @disconnected = true
-    puts &quot;User #{self.inspect} disconnecting&quot; if self.class.log?
+    puts &quot;User #{inspect} disconnecting&quot; if self.class.log?
     
     # notify the remote side
     notify!('**remote__disconnecting**') rescue nil
@@ -85,70 +81,37 @@ class Legs
   end
   
   # send a notification to this user
-  def notify!(method, *args)
-    puts &quot;Notify #{self.__inspect}: #{method}(#{args.map { |i| i.inspect }.join(', ')})&quot; if self.__class.log?
-    self.__send_data!({'method' =&gt; method.to_s, 'params' =&gt; args, 'id' =&gt; nil})
+  def notify!(method, *args, &amp;blk)
+    puts &quot;Notify #{inspect}: #{method}(#{args.map { |i| i.inspect }.join(', ')})&quot; if self.class.log?
+    send_data!({'method' =&gt; method.to_s, 'params' =&gt; args, 'id' =&gt; nil})
   end
   
   # sends a normal RPC request that has a response
-  def send!(method, *args)
-    puts &quot;Call #{self.__inspect}: #{method}(#{args.map { |i| i.inspect }.join(', ')})&quot; if self.__class.log?
-    id = self.__get_unique_number
-    self.send_data! 'method' =&gt; method.to_s, 'params' =&gt; args, 'id' =&gt; id
-    
-    while @responses_mutex.synchronize { @responses.keys.include?(id) } == false
-      sleep(0.01)
-    end
-    
-    data = @responses_mutex.synchronize { @responses.delete(id) }
+  def send!(method, *args, &amp;blk)
+    puts &quot;Call #{inspect}: #{method}(#{args.map { |i| i.inspect }.join(', ')})&quot; if self.class.log?
+    id = get_unique_number
+    send_data! 'method' =&gt; method.to_s, 'params' =&gt; args, 'id' =&gt; id
     
-    error = data['error']
-    raise error unless error.nil?
-    
-    puts &quot;&gt;&gt; #{method} #=&gt; #{data['result'].inspect}&quot; if self.__class.log?
-    
-    return data['result']
-  end
-  
-  def inspect
-    &quot;&lt;Legs:#{__object_id} Meta: #{@meta.inspect}&gt;&quot;
-  end
-  
-  # does an async request which calls a block when response arrives
-  def send_async!(method, *args, &amp;blk)
-    puts &quot;Call #{self.__inspect}: #{method}(#{args.map { |i| i.inspect }.join(', ')})&quot; if self.__class.log?
-    id = self.__get_unique_number
-    self.send_data! 'method' =&gt; method.to_s, 'params' =&gt; args, 'id' =&gt; id
-    
-    Thread.new do
-      while @responses_mutex.synchronize { @responses.keys.include?(id) } == false
-        sleep(0.05)
-      end
+    worker = Proc.new do
+      sleep 0.1 until @responses_mutex.synchronize { @responses.keys.include?(id) }
       
-      data = @responses_mutex.synchronize { @responses.delete(id) }
-      puts &quot;&gt;&gt; #{method} #=&gt; #{data['result'].inspect}&quot; if self.__class.log? unless data['error']
-      blk[Legs::AsyncData.new(data)]
+      result = Legs::Result.new(@responses_mutex.synchronize { @responses.delete(id) })
+      puts &quot;&gt;&gt; #{method} #=&gt; #{result.data['result'].inspect}&quot; if self.class.log?
+      result
     end
+    
+    if blk.respond_to?(:call); Thread.new { blk[worker.call] }
+    else; worker.call.value; end
   end
   
-  # maps undefined methods in to rpc calls
-  def method_missing(method, *args)
-    return self.send(method, *args) if method.to_s =~ /^__/
-    send!(method, *args)
-  end
+  # catch all the rogue calls and make them work niftily
+  alias_method :method_missing, :send!
   
-  # hacks the send method so ancestor methods instead become rpc calls too
-  # if you want to use a method in a Legs superclass, prefix with __
-  def send(method, *args)
-    return super(method.to_s.sub(/^__/, ''), *args) if method.to_s =~ /^__/
-    return super(method, *args) if self.__public_methods(false).include?(method)
-    super('send!', method.to_s, *args)
-  end
-
   # sends raw object over the socket
   def send_data!(data)
     raise &quot;Lost remote connection&quot; unless connected?
-    @socket_mutex.synchronize { @socket.write(JSON.generate(__json_marshal(data)) + self.__class.terminator) }
+    raw = JSON.generate(json_marshal(data)) + self.class.terminator
+    @socket_mutex.synchronize { @socket.write(raw) }
   end
   
   
@@ -161,17 +124,19 @@ class Legs
       return object
     when Hash
       out = Hash.new
-      object.each_pair { |k,v| out[k.to_s] = __json_marshal(v) }
+      object.each_pair { |k,v| out[k.to_s] = json_marshal(v) }
       return out
     when Array
-      return object.map { |v| __json_marshal(v) }
+      return object.map { |v| json_marshal(v) }
+    when Exception
+      return {'__jsonclass__' =&gt; ['RemoteError', &quot;&lt;#{object.class.name}&gt; #{object.message}&quot;, object.backtrace]}
     else
-      return {'__jsonclass__' =&gt; [object.class.name, object._dump]} if object.respond_to?(:_dump)
+      return {'__jsonclass__' =&gt; [object.class.name, '**_dump**', object._dump]} if object.respond_to?(:_dump)
       
       # the default marshalling behaviour
       instance_vars = {}
       object.instance_variables.each do |var_name|
-        instance_vars[var_name.to_s.sub(/@/, '')] = self.__json_marshal(object.instance_variable_get(var_name))
+        instance_vars[var_name.to_s.sub(/@/, '')] = json_marshal(object.instance_variable_get(var_name))
       end
       
       return {'__jsonclass__' =&gt; [object.class.name]}.merge(instance_vars)
@@ -188,11 +153,16 @@ class Legs
         object_class = Module.const_get(class_name) rescue false
         
         if object_class.name == class_name
-          return object_class._load(*constructor) if object_class.respond_to?(:_load) unless constructor.empty?
+          return object_class._load(constructor.last) if object_class.respond_to?(:_load) and constructor.first == '**_dump**'
+          
+          if constructor.empty?
+            instance = object_class.allocate
+          else
+            instance = object_class.new(*constructor)
+          end
           
-          instance = object_class.allocate
           object.each_pair do |key, value|
-            instance.instance_variable_set(&quot;@#{key}&quot;, self.__json_restore(value))
+            instance.instance_variable_set(&quot;@#{key}&quot;, json_restore(value))
           end
           return instance
         else
@@ -200,11 +170,11 @@ class Legs
         end
       else
         hash = Hash.new
-        object.each_pair { |k,v| hash[k] = self.__json_restore(v) }
+        object.each_pair { |k,v| hash[k] = json_restore(v) }
         return hash
       end
     when Array
-      return object.map { |i| self.__json_restore(i) }
+      return object.map { |i| json_restore(i) }
     else
       return object
     end
@@ -214,6 +184,12 @@ class Legs
   def get_unique_number; @unique_id ||= 0; @unique_id += 1; end
 end
 
+# undef's the superclass's methods so they won't get in the way
+removal_list = Legs.instance_methods(true)
+removal_list -= %w{JSON new class object_id send __send__ __id__ &lt; &lt;= &lt;=&gt; =&gt; &gt; == === yield raise}
+removal_list -= Legs.instance_methods(false)
+Legs.class_eval { removal_list.each { |m| undef_method m } }
+
 
 # the server is started by subclassing Legs, then SubclassName.start
 class &lt;&lt; Legs
@@ -221,10 +197,11 @@ class &lt;&lt; Legs
   attr_reader :incoming, :outgoing, :server_object, :incoming_mutex, :outgoing_mutex, :messages_mutex
   alias_method :log?, :log
   alias_method :users, :incoming
+  def started?; @started; end
   
   def initializer
     ObjectSpace.define_finalizer(self) { self.stop! }
-    @incoming = []; @outgoing = []; @messages = Queue.new; @terminator = &quot;\n&quot;; @log = true
+    @incoming = []; @outgoing = []; @messages = Queue.new; @terminator = &quot;\n&quot;; @log = false
     @incoming_mutex = Mutex.new; @outgoing_mutex = Mutex.new
   end
   
@@ -232,8 +209,8 @@ class &lt;&lt; Legs
   # starts the server, pass nil for port to make a 'server' that doesn't actually accept connections
   # This is useful for adding methods to Legs so that systems you connect to can call methods back on you
   def start(port=30274, &amp;blk)
-    return if started?
-    raise &quot;Legs.start requires a block&quot; unless blk
+    @server_class.module_eval(&amp;blk) and return if started?
+    raise &quot;Legs.start requires a block&quot; unless block_given?
     @started = true
     
     # make the fake class
@@ -259,7 +236,7 @@ class &lt;&lt; Legs
       
       # Finds a user by the value of a certain property... like find_user_by :object_id, 12345
       def find_user_by_object_id value
-        server.incoming.find { |user| user.__object_id == value }
+        server.incoming.find { |user| user.object_id == value }
       end
       
       # finds user's with the specified meta keys matching the specified values, can use regexps and stuff, like a case block
@@ -279,40 +256,43 @@ class &lt;&lt; Legs
     
     @message_processor = Thread.new do
       while started?
-        sleep(0.01) and next if @messages.empty?
+        sleep 0.01 while @messages.empty?
         data, from = @messages.deq
         method = data['method']; params = data['params']
         methods = @server_object.public_methods(false)
         
         # close dead connections
-        from.close! and next if data['method'] == '**remote__disconnecting**'
-        
-        begin
-          raise &quot;Supplied method is not a String&quot; unless method.is_a?(String)
-          raise &quot;Supplied params object is not an Array&quot; unless params.is_a?(Array)
-          raise &quot;Cannot run '#{method}' because it is not defined in this server&quot; unless methods.include?(method.to_s) or methods.include? :method_missing
-          
-          puts &quot;Call #{method}(#{params.map { |i| i.inspect }.join(', ')})&quot; if log?
-          
-          @server_object.instance_variable_set(:@caller, from)
-          
-          result = nil
-          
-          @incoming_mutex.synchronize do
-            if methods.include?(method.to_s)
-              result = @server_object.__send__(method.to_s, *params)
-            else
-              result = @server_object.method_missing(method.to_s, *params)
+        if data['method'] == '**remote__disconnecting**'
+          from.close!
+          next
+        else
+          begin
+            raise &quot;Supplied method is not a String&quot; unless method.is_a?(String)
+            raise &quot;Supplied params object is not an Array&quot; unless params.is_a?(Array)
+            raise &quot;Cannot run '#{method}' because it is not defined in this server&quot; unless methods.include?(method.to_s) or methods.include? :method_missing
+            
+            puts &quot;Call #{method}(#{params.map { |i| i.inspect }.join(', ')})&quot; if log?
+            
+            @server_object.instance_variable_set(:@caller, from)
+            
+            result = nil
+            
+            @incoming_mutex.synchronize do
+              if methods.include?(method.to_s)
+                result = @server_object.__send__(method.to_s, *params)
+              else
+                result = @server_object.method_missing(method.to_s, *params)
+              end
             end
+            
+            puts &quot;&gt;&gt; #{method} #=&gt; #{result.inspect}&quot; if log?
+            
+            from.send_data!({'id' =&gt; data['id'], 'result' =&gt; result}) unless data['id'].nil?
+            
+          rescue Exception =&gt; e
+            from.send_data!({'error' =&gt; e, 'id' =&gt; data['id']}) unless data['id'].nil?
+            puts &quot;Error: #{e}\nBacktrace: &quot; + e.backtrace.join(&quot;\n   &quot;) if log?
           end
-          
-          puts &quot;&gt;&gt; #{method} #=&gt; #{result.inspect}&quot; if log?
-          
-          from.send_data!({'id' =&gt; data['id'], 'result' =&gt; result}) unless data['id'].nil?
-          
-        rescue Exception =&gt; e
-          from.send_data!({'error' =&gt; e, 'id' =&gt; data['id']}) unless data['id'].nil?
-          puts &quot;Backtrace: \n&quot; + e.backtrace.map { |i| &quot;   #{i}&quot; }.join(&quot;\n&quot;) if log?
         end
       end
     end
@@ -353,14 +333,10 @@ class &lt;&lt; Legs
     @messages.enq [data, from]
   end
   
-  # returns true if server is running
-  def started?; @started; end
-  
-  # creates a legs client, and passes it to supplied block, closes client after block finishes running
-  # I wouldn't have added this method to keep shoes small, but users insist comedic value makes it worthwhile
-  def open(*args, &amp;blk)
+  # People say this syntax is too funny not to have... whatever. Works like IO and File and what have you
+  def open(*args)
     client = Legs.new(*args)
-    blk[client]
+    yield(client)
     client.close!
   end
   
@@ -376,15 +352,25 @@ end
 
 Legs.initializer
 
-
-class Legs::AsyncData
+# represents the data response, handles throwing of errors and stuff
+class Legs::Result
+  attr_reader :data
   def initialize(data); @data = data; end
   def result
-    @errored = true and raise @data['error'] if @data['error'] unless @errored
-    return @data['result']
+    unless @data['error'].nil? or @errored
+      @errored = true
+      raise @data['error']
+    end
+    @data['result']
   end
   alias_method :value, :result
 end
 
 class Legs::StartBlockError &lt; StandardError; end
 class Legs::RequestError &lt; StandardError; end
+class RemoteError &lt; StandardError
+  def initialize(msg, backtrace)
+    super(msg)
+    set_backtrace(backtrace)
+  end
+end</diff>
      <filename>lib/legs.rb</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>test/tester.rb</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>8b99facbb09271e679aebb413f6707e96eabb3ef</id>
    </parent>
  </parents>
  <author>
    <name>Jenna Fox</name>
    <email>blueberry@creativepony.com</email>
  </author>
  <url>http://github.com/Bluebie/legs/commit/024661db5eaed53194b0e05c3d160bb9be197fc2</url>
  <id>024661db5eaed53194b0e05c3d160bb9be197fc2</id>
  <committed-date>2008-07-15T08:56:35-07:00</committed-date>
  <authored-date>2008-07-15T08:56:35-07:00</authored-date>
  <message>Switched to ZenTest from my nasty putsy contraption, and found tons of bugs, fixed every last one of them, and changed the way instances of Legs work (what you might think of as a client) to work much better and much closer to how it was intended. More stuff is tested by the new test/test_legs.rb script than ever before, all the essentials for sure! I'll keep adding tests as I fiddle with legs and hopefully find some more bugs to squish! Legs seems pretty darned stable now though, probably pretty safe to start really messing with and enjoying. :)</message>
  <tree>ab25cc219a17a71126276e01bb7f24ef90f5fdf3</tree>
  <committer>
    <name>Jenna Fox</name>
    <email>blueberry@creativepony.com</email>
  </committer>
</commit>
