Ruby support for Thrift

Summary: Just client support so far.

Reviewed By: tbr-doug


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@664953 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/compiler/cpp/Makefile.am b/compiler/cpp/Makefile.am
index 1678c26..884a935 100644
--- a/compiler/cpp/Makefile.am
+++ b/compiler/cpp/Makefile.am
@@ -11,8 +11,9 @@
                  src/generate/t_cpp_generator.cc \
                  src/generate/t_java_generator.cc \
                  src/generate/t_php_generator.cc \
-                 src/generate/t_xsd_generator.cc \
-                 src/generate/t_py_generator.cc
+                 src/generate/t_py_generator.cc \
+                 src/generate/t_rb_generator.cc \
+                 src/generate/t_xsd_generator.cc
 
 thrift_CXXFLAGS = -Wall -Isrc
 thrift_LDFLAGS = -Wall
diff --git a/compiler/cpp/src/generate/t_rb_generator.cc b/compiler/cpp/src/generate/t_rb_generator.cc
new file mode 100644
index 0000000..b9f12ec
--- /dev/null
+++ b/compiler/cpp/src/generate/t_rb_generator.cc
@@ -0,0 +1,1573 @@
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sstream>
+#include "t_rb_generator.h"
+using namespace std;
+
+/**
+ * Prepares for file generation by opening up the necessary file output
+ * streams.
+ *
+ * @param tprogram The program to generate
+ */
+void t_rb_generator::init_generator() {
+  // Make output directory
+  mkdir(T_RB_DIR, S_IREAD | S_IWRITE | S_IEXEC);
+
+  // Make output file
+  string f_types_name = string(T_RB_DIR)+"/"+program_name_+"_types.rb";
+  f_types_.open(f_types_name.c_str());
+
+  string f_consts_name = string(T_RB_DIR)+"/"+program_name_+"_constants.rb";
+  f_consts_.open(f_consts_name.c_str());
+
+  // Print header
+  f_types_ <<
+    rb_autogen_comment() << endl <<
+    rb_imports() << endl <<
+    render_includes() << endl;
+  
+  f_consts_ <<
+    rb_autogen_comment() << endl <<
+    rb_imports() << endl <<
+    "require '" << program_name_ << "_types'" << endl <<
+    endl;
+}
+
+/**
+ * Renders all the imports necessary for including another Thrift program
+ */
+string t_rb_generator::render_includes() {
+  const vector<t_program*>& includes = program_->get_includes();
+  string result = "";
+  for (size_t i = 0; i < includes.size(); ++i) {
+    result += "require '" + includes[i]->get_name() + "_types'\n";
+  }
+  if (includes.size() > 0) {
+    result += "\n";
+  }
+  return result;
+}
+
+/**
+ * Autogen'd comment
+ */
+string t_rb_generator::rb_autogen_comment() {
+  return
+    std::string("#\n") +
+    "# Autogenerated by Thrift\n" +
+    "#\n" +
+    "# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" +
+    "#\n";
+}
+
+/**
+ * Prints standard thrift imports
+ */
+string t_rb_generator::rb_imports() {
+  return
+    string("require 'thrift/protocol/tprotocol'");
+}
+
+/**
+ * Closes the type files
+ */
+void t_rb_generator::close_generator() {
+  // Close types file
+  f_types_.close();
+  f_consts_.close();
+}
+
+/**
+ * Generates a typedef. This is not done in Ruby, types are all implicit.
+ *
+ * @param ttypedef The type definition
+ */
+void t_rb_generator::generate_typedef(t_typedef* ttypedef) {}
+
+/**
+ * Generates code for an enumerated type. Done using a class to scope
+ * the values.
+ *
+ * @param tenum The enumeration
+ */
+void t_rb_generator::generate_enum(t_enum* tenum) {
+  f_types_ <<
+    "module " << tenum->get_name() << endl;
+  indent_up();
+  
+  vector<t_enum_value*> constants = tenum->get_constants();
+  vector<t_enum_value*>::iterator c_iter;
+  int value = -1;
+  for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) {
+    if ((*c_iter)->has_value()) {
+      value = (*c_iter)->get_value();
+    } else {
+      ++value;
+    }
+    
+    // Ruby class constants have to be capitalized... omg i am so on the fence
+    // about languages strictly enforcing capitalization why can't we just all
+    // agree and play nice.
+    string name = capitalize((*c_iter)->get_name());
+    
+    f_types_ <<
+      indent() << name << " = " << value << endl;
+  }
+
+  indent_down();
+  f_types_ <<
+    "end" << endl <<
+    endl;
+}
+
+/**
+ * Generate a constant value
+ */
+void t_rb_generator::generate_const(t_const* tconst) {
+  t_type* type = tconst->get_type();
+  string name = tconst->get_name();
+  t_const_value* value = tconst->get_value();
+  
+  name[0] = toupper(name[0]);
+
+  indent(f_consts_) << name << " = "; 
+  print_const_value(type, value);
+  f_consts_ << endl << endl;
+}
+
+/**
+ * Prints the value of a constant with the given type. Note that type checking
+ * is NOT performed in this function as it is always run beforehand using the
+ * validate_types method in main.cc
+ */
+void t_rb_generator::print_const_value(t_type* type, t_const_value* value) {
+  if (type->is_base_type()) {
+    t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
+    switch (tbase) {
+    case t_base_type::TYPE_STRING:
+      f_consts_ << "'" << value->get_string() << "'";
+      break;
+    case t_base_type::TYPE_BOOL:
+      f_consts_ << (value->get_integer() > 0 ? "true" : "false");
+      break;
+    case t_base_type::TYPE_BYTE:
+    case t_base_type::TYPE_I16:
+    case t_base_type::TYPE_I32:
+    case t_base_type::TYPE_I64:
+      f_consts_ << value->get_integer();
+      break;
+    case t_base_type::TYPE_DOUBLE:
+      if (value->get_type() == t_const_value::CV_INTEGER) {
+        f_consts_ << value->get_integer();
+      } else {
+        f_consts_ << value->get_double();
+      }
+      break;
+    default:
+      throw "compiler error: no const of base type " + tbase;
+    }
+  } else if (type->is_enum()) {
+    indent(f_consts_) << value->get_integer();
+  } else if (type->is_struct() || type->is_xception()) {
+    f_consts_ << type->get_name() << "({" << endl;
+    indent_up();
+    const vector<t_field*>& fields = ((t_struct*)type)->get_members();
+    vector<t_field*>::const_iterator f_iter;
+    const map<t_const_value*, t_const_value*>& val = value->get_map();
+    map<t_const_value*, t_const_value*>::const_iterator v_iter;
+    for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
+      t_type* field_type = NULL;
+      for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+        if ((*f_iter)->get_name() == v_iter->first->get_string()) {
+          field_type = (*f_iter)->get_type();
+        }
+      }
+      if (field_type == NULL) {
+        throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string();
+      }
+      f_consts_ << indent();
+      print_const_value(g_type_string, v_iter->first);
+      f_consts_ << " => ";
+      print_const_value(field_type, v_iter->second);
+      f_consts_ << "," << endl;
+    }
+    indent_down();
+    indent(f_consts_) << "})";
+  } else if (type->is_map()) {
+    t_type* ktype = ((t_map*)type)->get_key_type();
+    t_type* vtype = ((t_map*)type)->get_val_type();
+    f_consts_ << "{" << endl;
+    indent_up();
+    const map<t_const_value*, t_const_value*>& val = value->get_map();
+    map<t_const_value*, t_const_value*>::const_iterator v_iter;
+    for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
+      f_consts_ << indent();
+      print_const_value(ktype, v_iter->first);
+      f_consts_ << " =>x ";
+      print_const_value(vtype, v_iter->second);
+      f_consts_ << "," << endl;
+    }
+    indent_down();
+    indent(f_consts_) << "}";
+  } else if (type->is_list() || type->is_set()) {
+    t_type* etype;
+    if (type->is_list()) {
+      etype = ((t_list*)type)->get_elem_type();
+    } else {
+      etype = ((t_set*)type)->get_elem_type();
+    }
+    if (type->is_set()) {
+      f_consts_ << "{";
+    } else {
+      f_consts_ << "[" << endl;
+    }
+    indent_up();
+    const vector<t_const_value*>& val = value->get_list();
+    vector<t_const_value*>::const_iterator v_iter;
+    for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) {
+      f_consts_ << indent();
+      print_const_value(etype, *v_iter);
+      if (type->is_set()) {
+        f_consts_ << " => true";
+      }
+      f_consts_ << "," << endl;
+    }
+    indent_down();
+    if (type->is_set()) {
+      indent(f_consts_) << "}";
+    } else {
+      indent(f_consts_) << "]";
+    }
+  }
+}
+
+/**
+ * Generates a ruby struct
+ */
+void t_rb_generator::generate_struct(t_struct* tstruct) {
+  generate_rb_struct(tstruct, false);
+}
+
+/**
+ * Generates a struct definition for a thrift exception. Basically the same
+ * as a struct but extends the Exception class.
+ *
+ * @param txception The struct definition
+ */
+void t_rb_generator::generate_xception(t_struct* txception) {
+  generate_rb_struct(txception, true);  
+}
+
+/**
+ * Generates a ruby struct
+ */
+void t_rb_generator::generate_rb_struct(t_struct* tstruct,
+                                        bool is_exception) {
+  generate_rb_struct_definition(f_types_, tstruct, is_exception);
+}
+
+/**
+ * Generates a struct definition for a thrift data type. This is nothing in PHP
+ * where the objects are all just associative arrays (unless of course we
+ * decide to start using objects for them...)
+ *
+ * @param tstruct The struct definition
+ */
+void t_rb_generator::generate_rb_struct_definition(ofstream& out,
+                                                   t_struct* tstruct,
+                                                   bool is_exception,
+                                                   bool is_result) {
+  const vector<t_field*>& members = tstruct->get_members();
+  vector<t_field*>::const_iterator m_iter; 
+
+  indent(out) <<
+    "class " << type_name(tstruct);
+  if (is_exception) {
+    out << " < StandardError";
+  }
+  out << endl;
+  indent_up();
+
+  out << endl;
+
+  if (members.size() > 0) {
+    indent(out) << "attr_writer ";
+    bool first = true;
+    for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+      if (first) {
+        first = false;
+      } else {
+        out << ", ";
+      }
+      out << ":" << (*m_iter)->get_name();
+    }
+    out << endl;
+    indent(out) << "attr_reader ";
+    first = true;
+    for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+      if (first) {
+        first = false;
+      } else {
+        out << ", ";
+      }
+      out << ":" << (*m_iter)->get_name();
+    }
+    out << endl;
+    out << endl;
+  }
+
+  out <<
+    indent() << "def initialize(d=nil)" << endl;
+  indent_up();
+
+  if (members.size() > 0) {
+    indent(out) <<
+      "if (d != nil)" << endl;
+    indent_up();
+    for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) {
+      out <<
+        indent() << "if (d.has_key?('" << (*m_iter)->get_name() << "'))" << endl <<
+        indent() << "  @" << (*m_iter)->get_name() << " = d['" << (*m_iter)->get_name() << "']" << endl <<
+        indent() << "end" << endl;
+    }
+    indent_down();
+    indent(out) << "end" << endl;
+  }
+  
+  indent_down();
+  indent(out) << "end" << endl;
+ 
+  out << endl;
+
+  generate_rb_struct_reader(out, tstruct);
+  generate_rb_struct_writer(out, tstruct);
+
+  indent_down();
+  indent(out) << "end" << endl << endl;
+}
+
+/**
+ * Generates the read method for a struct
+ */
+void t_rb_generator::generate_rb_struct_reader(ofstream& out,
+                                                t_struct* tstruct) {
+  const vector<t_field*>& fields = tstruct->get_members();
+  vector<t_field*>::const_iterator f_iter;
+
+  indent(out) <<
+    "def read(iprot)" << endl;
+  indent_up();
+    
+  indent(out) <<
+    "iprot.readStructBegin()" << endl;   
+
+  // Loop over reading in fields
+  indent(out) <<
+    "while true" << endl;
+    indent_up();
+    
+    // Read beginning field marker
+    indent(out) <<
+      "fname, ftype, fid = iprot.readFieldBegin()" << endl;
+
+    // Check for field STOP marker and break
+    indent(out) <<
+      "if (ftype === TType::STOP)" << endl;
+    indent_up();
+    indent(out) <<
+      "break" << endl;
+    indent_down();
+    if (fields.size() > 0) {
+      indent(out) <<
+        "end" << endl;
+    }
+  
+    // Switch statement on the field we are reading
+    bool first = true;
+    
+    // Generate deserialization code for known cases
+    for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+      if (first) {
+        first = false;
+        out <<
+          indent() << "if ";
+      } else {
+        out <<
+          indent() << "elsif ";
+      }
+      out << "(fid == " << (*f_iter)->get_key() << ")" << endl;
+      indent_up();
+      generate_deserialize_field(out, *f_iter, "@");
+      indent_down();
+    }
+    
+    // In the default case we skip the field
+    out <<
+      indent() << "else" << endl <<
+      indent() << "  iprot.skip(ftype)" << endl <<
+      indent() << "end" << endl; 
+      
+    // Read field end marker
+    indent(out) <<
+      "iprot.readFieldEnd()" << endl;
+    
+    indent_down();
+    indent(out) << "end" << endl;
+
+    indent(out) <<
+      "iprot.readStructEnd()" << endl;
+
+    indent_down();
+    indent(out) << "end" << endl;
+  out << endl;
+}
+
+void t_rb_generator::generate_rb_struct_writer(ofstream& out,
+                                               t_struct* tstruct) {
+  string name = tstruct->get_name();
+  const vector<t_field*>& fields = tstruct->get_members();
+  vector<t_field*>::const_iterator f_iter;
+
+  indent(out) <<
+    "def write(oprot)" << endl;
+  indent_up();
+  
+  indent(out) <<
+    "oprot.writeStructBegin('" << name << "')" << endl;
+
+  for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+    // Write field header
+    indent(out) <<
+      "if (@" << (*f_iter)->get_name() << " != nil)" << endl;
+    indent_up();
+    indent(out) <<
+      "oprot.writeFieldBegin(" <<
+      "'" << (*f_iter)->get_name() << "', " <<
+      type_to_enum((*f_iter)->get_type()) << ", " <<
+      (*f_iter)->get_key() << ")" << endl;
+
+    // Write field contents
+    generate_serialize_field(out, *f_iter, "@");
+
+    // Write field closer
+    indent(out) <<
+      "oprot.writeFieldEnd()" << endl;
+
+    indent_down();
+    indent(out) << "end" << endl;
+  }
+
+  // Write the struct map
+  out <<
+    indent() << "oprot.writeFieldStop()" << endl <<
+    indent() << "oprot.writeStructEnd()" << endl;
+
+  indent_down();
+  indent(out) << "end" << endl;
+
+  out <<
+    endl;
+}
+
+/**
+ * Generates a thrift service.
+ *
+ * @param tservice The service definition
+ */
+void t_rb_generator::generate_service(t_service* tservice) {
+  string f_service_name = string(T_RB_DIR)+"/"+service_name_+".rb";
+  f_service_.open(f_service_name.c_str());
+
+  f_service_ <<
+    rb_autogen_comment() << endl <<
+    rb_imports() << endl;
+
+  if (tservice->get_extends() != NULL) {
+    f_service_ <<
+      "require '" << tservice->get_extends()->get_name() << "'" << endl;
+  }
+
+  f_service_ <<
+    "require 'thrift/thrift'" << endl <<
+    "require '" << program_name_ << "_types'" << endl << 
+    endl;
+
+  f_service_ << "module " << tservice->get_name() << endl;
+  indent_up();
+
+  // Generate the three main parts of the service (well, two for now in PHP)
+  generate_service_interface(tservice);
+  generate_service_client(tservice);
+  generate_service_server(tservice);
+  generate_service_helpers(tservice);
+  generate_service_remote(tservice);
+
+  indent_down();
+  f_service_ << "end" << endl <<
+    endl;
+
+  // Close service file
+  f_service_.close();
+}
+
+/**
+ * Generates helper functions for a service.
+ *
+ * @param tservice The service to generate a header definition for
+ */
+void t_rb_generator::generate_service_helpers(t_service* tservice) {
+  vector<t_function*> functions = tservice->get_functions();
+  vector<t_function*>::iterator f_iter;
+
+  indent(f_service_) <<
+    "# HELPER FUNCTIONS AND STRUCTURES" << endl << endl;
+
+  for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+    t_struct* ts = (*f_iter)->get_arglist();
+    generate_rb_struct_definition(f_service_, ts, false);
+    generate_rb_function_helpers(*f_iter);
+  }
+}
+
+/**
+ * Generates a struct and helpers for a function.
+ *
+ * @param tfunction The function
+ */
+void t_rb_generator::generate_rb_function_helpers(t_function* tfunction) {
+  t_struct result(program_, tfunction->get_name() + "_result");
+  t_field success(tfunction->get_returntype(), "success", 0);
+  if (!tfunction->get_returntype()->is_void()) {
+    result.append(&success);
+  }
+
+  t_struct* xs = tfunction->get_xceptions();
+  const vector<t_field*>& fields = xs->get_members();
+  vector<t_field*>::const_iterator f_iter;
+  for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+    result.append(*f_iter);
+  }
+  generate_rb_struct_definition(f_service_, &result, false, true);
+}
+
+/**
+ * Generates a service interface definition.
+ *
+ * @param tservice The service to generate a header definition for
+ */
+void t_rb_generator::generate_service_interface(t_service* tservice) {
+  f_service_ <<
+    indent() << "module Iface" << endl;
+  indent_up();
+
+  if (tservice->get_extends() != NULL) {
+    string extends = type_name(tservice->get_extends());
+    indent(f_service_) << "include " << extends  << ".Iface" << endl;
+  }
+
+  vector<t_function*> functions = tservice->get_functions();
+  vector<t_function*>::iterator f_iter; 
+  for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+    f_service_ <<
+      indent() << "def " << function_signature(*f_iter) << "" << endl <<
+      indent() << "end" << endl << endl;
+  }
+  indent_down();
+  indent(f_service_) << "end" << endl << endl;
+}
+
+/**
+ * Generates a service client definition.
+ *
+ * @param tservice The service to generate a server for.
+ */
+void t_rb_generator::generate_service_client(t_service* tservice) {
+  string extends = "";
+  string extends_client = "";
+  if (tservice->get_extends() != NULL) {
+    extends = type_name(tservice->get_extends());
+    extends_client = " < " + extends + ".Client, ";
+  }
+
+  indent(f_service_) <<
+    "class Client" << extends_client << endl;
+  indent_up();
+
+  indent(f_service_) <<
+    "include Iface" << endl << endl;
+  
+  // Constructor function
+  f_service_ <<
+    indent() << "def initialize(iprot, oprot=nil)" << endl;
+  if (extends.empty()) {
+    f_service_ <<
+      indent() << "  @iprot = @oprot = iprot" << endl <<
+      indent() << "  if (oprot != nil)" << endl <<
+      indent() << "    @oprot = oprot" << endl <<
+      indent() << "  end" << endl <<
+      indent() << "  @seqid = 0" << endl;
+  }
+  indent(f_service_) << "end" << endl << endl;
+
+  // Generate client method implementations
+  vector<t_function*> functions = tservice->get_functions();
+  vector<t_function*>::const_iterator f_iter;    
+  for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+    t_struct* arg_struct = (*f_iter)->get_arglist();
+    const vector<t_field*>& fields = arg_struct->get_members();
+    vector<t_field*>::const_iterator fld_iter;
+    string funname = (*f_iter)->get_name();
+
+    // Open function
+    indent(f_service_) <<
+      "def " << function_signature(*f_iter) << endl;
+    indent_up();
+      indent(f_service_) <<
+        "send_" << funname << "(";
+
+      bool first = true;
+      for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) {
+        if (first) {
+          first = false;
+        } else {
+          f_service_ << ", ";
+        }
+        f_service_ << (*fld_iter)->get_name();
+      }
+      f_service_ << ")" << endl;
+
+      if (!(*f_iter)->is_async()) {
+        f_service_ << indent();
+        if (!(*f_iter)->get_returntype()->is_void()) {
+          f_service_ << "return ";
+        }
+        f_service_ <<
+          "recv_" << funname << "()" << endl;
+      }
+    indent_down();
+    indent(f_service_) << "end" << endl;
+    f_service_ << endl;
+
+    indent(f_service_) <<
+      "def send_" << function_signature(*f_iter) << endl;
+    indent_up();
+
+      std::string argsname = capitalize((*f_iter)->get_name() + "_args");
+
+      // Serialize the request header
+      f_service_ <<
+        indent() << "@oprot.writeMessageBegin('" << (*f_iter)->get_name() << "', TMessageType::CALL, @seqid)" << endl;
+      
+      f_service_ <<
+        indent() << "args = " << argsname << ".new()" << endl;
+      
+      for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) {
+        f_service_ <<
+          indent() << "args." << (*fld_iter)->get_name() << " = " << (*fld_iter)->get_name() << endl;
+      }
+           
+      // Write to the stream
+      f_service_ <<
+        indent() << "args.write(@oprot)" << endl <<
+        indent() << "@oprot.writeMessageEnd()" << endl <<
+        indent() << "@oprot.trans.flush()" << endl;  
+
+    indent_down();
+    indent(f_service_) << "end" << endl;
+
+    if (!(*f_iter)->is_async()) {
+      std::string resultname = capitalize((*f_iter)->get_name() + "_result");
+      t_struct noargs(program_);
+      
+      t_function recv_function((*f_iter)->get_returntype(),
+                               string("recv_") + (*f_iter)->get_name(),
+                               &noargs);
+      // Open function
+      f_service_ <<
+        endl <<
+        indent() << "def " << function_signature(&recv_function) << endl;
+      indent_up();
+
+      f_service_ <<
+        indent() << "fname, mtype, rseqid = @iprot.readMessageBegin()" << endl;
+
+      // TODO(mcslee): Validate message reply here, seq ids etc.
+
+      f_service_ <<
+        indent() << "result = " << resultname << ".new()" << endl <<
+        indent() << "result.read(@iprot)" << endl <<
+        indent() << "@iprot.readMessageEnd()" << endl;
+
+      // Careful, only return _result if not a void function
+      if (!(*f_iter)->get_returntype()->is_void()) {
+        f_service_ <<
+          indent() << "if result.success != nil" << endl <<
+          indent() << "  return result.success" << endl <<
+          indent() << "end" << endl;          
+      }
+
+      t_struct* xs = (*f_iter)->get_xceptions();
+      const std::vector<t_field*>& xceptions = xs->get_members();
+      vector<t_field*>::const_iterator x_iter;
+      for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) {
+        f_service_ <<
+          indent() << "if result." << (*x_iter)->get_name() << " != nil" << endl <<
+          indent() << "  raise result." << (*x_iter)->get_name() << "" << endl <<
+          indent() << "end" << endl;
+      }
+
+      // Careful, only return _result if not a void function
+      if ((*f_iter)->get_returntype()->is_void()) {
+        indent(f_service_) <<
+          "return" << endl;
+      } else {
+        f_service_ <<
+          indent() << "raise StandardError.new('" << (*f_iter)->get_name() << " failed: unknown result')" << endl;
+      }     
+
+      // Close function
+      indent_down();
+      indent(f_service_) << "end" << endl << endl;
+    }      
+  }
+
+  indent_down();
+  indent(f_service_) << "end" << endl << endl;
+}
+
+/**
+ * Generates a command line tool for making remote requests
+ *
+ * @param tservice The service to generate a remote for.
+ */
+void t_rb_generator::generate_service_remote(t_service* tservice) {
+  vector<t_function*> functions = tservice->get_functions();
+  vector<t_function*>::iterator f_iter; 
+
+  string f_remote_name = string(T_RB_DIR)+"/"+service_name_+"-remote";
+  ofstream f_remote;
+  f_remote.open(f_remote_name.c_str());
+
+  f_remote <<
+    "#!/usr/bin/ruby" << endl <<
+    rb_autogen_comment() << endl <<
+    "import sys" << endl <<
+    "import pprint" << endl <<
+    "from thrift.transport import TTransport" << endl <<
+    "from thrift.transport import TSocket" << endl <<
+    "from thrift.protocol import TBinaryProtocol" << endl <<
+    endl;
+
+  f_remote <<
+    "import " << service_name_ << endl <<
+    "from " << program_name_ << "_types import *" << endl << 
+    endl;
+
+  f_remote <<
+    "if len(sys.argv) <= 1 or sys.argv[1] == '--help':" << endl <<
+    "  print ''" << endl <<
+    "  print 'Usage: ' + sys.argv[0] + ' [-h host:port] [-f[ramed]] function [arg1,[arg2...]]'" << endl <<
+    "  print ''" << endl <<
+    "  print 'Functions:'" << endl;
+  for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+    f_remote <<
+      "  print '  " << (*f_iter)->get_returntype()->get_name() << " " << (*f_iter)->get_name() << "(";
+    t_struct* arg_struct = (*f_iter)->get_arglist();
+    const std::vector<t_field*>& args = arg_struct->get_members();
+    vector<t_field*>::const_iterator a_iter;
+    int num_args = args.size();
+    bool first = true;
+    for (int i = 0; i < num_args; ++i) {
+      if (first) {
+        first = false;
+      } else {
+        f_remote << ", ";
+      }
+      f_remote << 
+        args[i]->get_type()->get_name() << " " << args[i]->get_name();
+    }
+    f_remote << ")'" << endl;
+  }  
+  f_remote <<
+    "  print ''" << endl <<
+    "  sys.exit(0)" << endl <<
+    endl;
+
+  f_remote <<
+    "pp = pprint.PrettyPrinter(indent = 2)" << endl <<
+    "host = 'localhost'" << endl <<
+    "port = 9090" << endl <<
+    "framed = False" << endl <<
+    "argi = 1" << endl <<
+    endl <<
+    "if sys.argv[1] == '-h':" << endl <<
+    "  parts = sys.argv[2].split(':') " << endl <<
+    "  host = parts[0]" << endl <<
+    "  port = int(parts[1])" << endl <<
+    "  argi = 3" << endl <<
+    endl <<
+    "if sys.argv[argi] == '-f' or sys.argv[argi] == '-framed':" << endl <<
+    "  framed = True" << endl <<
+    "  argi += 1" << endl <<
+    endl <<
+    "cmd = sys.argv[argi]" << endl <<
+    "args = sys.argv[argi+1:]" << endl <<
+    endl <<
+    "socket = TSocket.TSocket(host, port)" << endl <<
+    "if framed:" << endl <<
+    "  transport = TTransport.TFramedTransport(socket)" << endl <<
+    "else:" << endl <<
+    "  transport = TTransport.TBufferedTransport(socket)" << endl <<
+    "protocol = TBinaryProtocol.TBinaryProtocol(transport)" << endl <<
+    "client = " << service_name_ << ".Client(protocol)" << endl <<
+    "transport.open()" << endl <<
+    endl;
+  
+  // Generate the dispatch methods
+  bool first = true;
+
+  for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+    if (first) {
+      first = false;
+    } else {
+      f_remote << "el";
+    }
+
+    t_struct* arg_struct = (*f_iter)->get_arglist();
+    const std::vector<t_field*>& args = arg_struct->get_members();
+    vector<t_field*>::const_iterator a_iter;
+    int num_args = args.size();
+
+    f_remote <<
+      "if cmd == '" << (*f_iter)->get_name() << "':" << endl <<
+      "  if len(args) != " << num_args << ":" << endl <<
+      "    print '" << (*f_iter)->get_name() << " requires " << num_args << " args'" << endl <<
+      "    sys.exit(1)" << endl <<
+      "  pp.pprint(client." << (*f_iter)->get_name() << "(";
+    for (int i = 0; i < num_args; ++i) {
+      if (args[i]->get_type()->is_string()) {
+        f_remote << "args[" << i << "],";
+      } else {
+        f_remote << "eval(args[" << i << "]),";
+      }
+    }
+    f_remote << "))" << endl;
+   
+    f_remote << endl;
+  }
+
+  f_remote << "transport.close()" << endl;
+  
+  // Close service file
+  f_remote.close();
+  
+  // Make file executable, love that bitwise OR action
+  chmod(f_remote_name.c_str(),
+        S_IRUSR |
+        S_IWUSR |
+        S_IXUSR |
+        S_IRGRP |
+        S_IXGRP |
+        S_IROTH |
+        S_IXOTH);
+}
+
+/**
+ * Generates a service server definition.
+ *
+ * @param tservice The service to generate a server for.
+ */
+void t_rb_generator::generate_service_server(t_service* tservice) {
+  // Generate the dispatch methods
+  vector<t_function*> functions = tservice->get_functions();
+  vector<t_function*>::iterator f_iter; 
+
+  string extends = "";
+  string extends_processor = "";
+  if (tservice->get_extends() != NULL) {
+    extends = type_name(tservice->get_extends());
+    extends_processor = " < " + extends + ".Processor, ";
+  }
+
+  // Generate the header portion
+  indent(f_service_) <<
+    "class Processor" << extends_processor << endl;
+  indent_up();
+
+  f_service_ <<
+    indent() << "include Iface" << endl <<
+    indent() << "include TProcessor" << endl <<
+    endl;
+
+  indent(f_service_) <<
+    "def initialize(handler)" << endl;
+  indent_up();
+  if (extends.empty()) {
+    f_service_ <<
+      indent() << "@handler = handler" << endl <<
+      indent() << "@processMap = {}" << endl;
+  }
+  for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+    f_service_ <<
+      indent() << "@processMap[\"" << (*f_iter)->get_name() << "\"] = Processor.process_" << (*f_iter)->get_name() << endl;
+  } 
+  indent_down();
+  indent(f_service_) << "end" << endl << endl;
+ 
+  // Generate the server implementation
+  indent(f_service_) <<
+    "def process(iprot, oprot)" << endl;
+  indent_up();
+
+  f_service_ <<
+    indent() << "name, type, seqid  = iprot.readMessageBegin()" << endl;
+
+  // TODO(mcslee): validate message
+
+  // HOT: dictionary function lookup
+  f_service_ <<
+    indent() << "if (@processMap.has_key?(name))" << endl <<
+    indent() << "  @processMap[name].call(seqid, iprot, oprot)" << endl <<
+    indent() << "else" << endl <<
+    indent() << "  print 'Unknown function %s' % (name)" << endl <<
+    indent() << "end" << endl;
+
+  // Read end of args field, the T_STOP, and the struct close
+  f_service_ <<
+    indent() << "return true" << endl;
+
+  indent_down();
+  indent(f_service_) << "end" << endl << endl;
+
+  // Generate the process subfunctions
+  for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) {
+    generate_process_function(tservice, *f_iter);
+  }
+
+  indent_down();
+  indent(f_service_) << "end" << endl << endl;
+}
+
+/**
+ * Generates a process function definition.
+ *
+ * @param tfunction The function to write a dispatcher for
+ */
+void t_rb_generator::generate_process_function(t_service* tservice,
+                                               t_function* tfunction) {
+  // Open function
+  indent(f_service_) <<
+    "def process_" << tfunction->get_name() <<
+    "(seqid, iprot, oprot)" << endl;
+  indent_up();
+
+  string argsname = tfunction->get_name() + "_args";
+  string resultname = tfunction->get_name() + "_result";
+
+  f_service_ <<
+    indent() << "args = " << argsname << "()" << endl <<
+    indent() << "args.read(iprot)" << endl <<
+    indent() << "iprot.readMessageEnd()" << endl;
+
+  t_struct* xs = tfunction->get_xceptions();
+  const std::vector<t_field*>& xceptions = xs->get_members();
+  vector<t_field*>::const_iterator x_iter;
+
+  // Declare result for non async function
+  if (!tfunction->is_async()) {
+    f_service_ <<
+      indent() << "result = " << resultname << "()" << endl;
+  }
+
+  // Try block for a function with exceptions
+  if (xceptions.size() > 0) {
+    f_service_ <<
+      indent() << "begin" << endl;
+    indent_up();
+  }
+ 
+  // Generate the function call
+  t_struct* arg_struct = tfunction->get_arglist();
+  const std::vector<t_field*>& fields = arg_struct->get_members();
+  vector<t_field*>::const_iterator f_iter;
+
+  f_service_ << indent();
+  if (!tfunction->is_async() && !tfunction->get_returntype()->is_void()) {
+    f_service_ << "result.success = ";
+  }
+  f_service_ <<
+    "@handler." << tfunction->get_name() << "(";
+  bool first = true;
+  for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+    if (first) {
+      first = false;
+    } else {
+      f_service_ << ", ";
+    }
+    f_service_ << "args." << (*f_iter)->get_name();
+  }
+  f_service_ << ")" << endl;
+
+  if (!tfunction->is_async() && xceptions.size() > 0) {
+    indent_down();
+    for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) {
+      f_service_ <<
+        indent() << "rescue " << (*x_iter)->get_type()->get_name() << " => " << (*x_iter)->get_name() << endl;
+      if (!tfunction->is_async()) {
+        indent_up();
+        f_service_ <<
+          indent() << "result." << (*x_iter)->get_name() << " = " << (*x_iter)->get_name() << endl;
+        indent_down();
+      }
+    }
+    indent(f_service_) << "end" << endl;
+  }
+
+  // Shortcut out here for async functions
+  if (tfunction->is_async()) {
+    f_service_ <<
+      indent() << "return" << endl;
+    indent_down();
+    indent(f_service_) << "end" << endl << endl;
+    return;
+  }
+
+  f_service_ <<
+    indent() << "oprot.writeMessageBegin('" << tfunction->get_name() << "', TMessageType::REPLY, seqid)" << endl <<
+    indent() << "result.write(oprot)" << endl <<
+    indent() << "oprot.writeMessageEnd()" << endl <<
+    indent() << "oprot.trans.flush()" << endl;
+
+  // Close function
+  indent_down();
+  indent(f_service_) << "end" << endl << endl;
+}
+
+/**
+ * Deserializes a field of any type.
+ */
+void t_rb_generator::generate_deserialize_field(ofstream &out,
+                                                t_field* tfield,
+                                                string prefix,
+                                                bool inclass) {
+  t_type* type = tfield->get_type();
+  while (type->is_typedef()) {
+    type = ((t_typedef*)type)->get_type();
+  }
+
+  if (type->is_void()) {
+    throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " +
+      prefix + tfield->get_name();
+  }
+
+  string name = prefix + tfield->get_name();
+
+  if (type->is_struct() || type->is_xception()) {
+    generate_deserialize_struct(out,
+                                (t_struct*)type,
+                                 name);
+  } else if (type->is_container()) {
+    generate_deserialize_container(out, type, name);
+  } else if (type->is_base_type() || type->is_enum()) {
+    indent(out) <<
+      name << " = iprot.";
+    
+    if (type->is_base_type()) {
+      t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
+      switch (tbase) {
+      case t_base_type::TYPE_VOID:
+        throw "compiler error: cannot serialize void field in a struct: " +
+          name;
+        break;
+      case t_base_type::TYPE_STRING:        
+        out << "readString();";
+        break;
+      case t_base_type::TYPE_BOOL:
+        out << "readBool();";
+        break;
+      case t_base_type::TYPE_BYTE:
+        out << "readByte();";
+        break;
+      case t_base_type::TYPE_I16:
+        out << "readI16();";
+        break;
+      case t_base_type::TYPE_I32:
+        out << "readI32();";
+        break;
+      case t_base_type::TYPE_I64:
+        out << "readI64();";
+        break;
+      case t_base_type::TYPE_DOUBLE:
+        out << "readDouble();";
+        break;
+      default:
+        throw "compiler error: no PHP name for base type " + tbase;
+      }
+    } else if (type->is_enum()) {
+      out << "readI32();";
+    }
+    out << endl;
+
+  } else {
+    printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n",
+           tfield->get_name().c_str(), type->get_name().c_str());
+  }  
+}
+
+/**
+ * Generates an unserializer for a struct, calling read()
+ */
+void t_rb_generator::generate_deserialize_struct(ofstream &out,
+                                                  t_struct* tstruct,
+                                                  string prefix) {
+  out <<
+    indent() << prefix << " = " << type_name(tstruct) << "()" << endl <<
+    indent() << prefix << ".read(iprot)" << endl;
+}
+
+/**
+ * Serialize a container by writing out the header followed by
+ * data and then a footer.
+ */
+void t_rb_generator::generate_deserialize_container(ofstream &out,
+                                                    t_type* ttype,
+                                                    string prefix) {
+  string size = tmp("_size");
+  string ktype = tmp("_ktype");
+  string vtype = tmp("_vtype");
+  string etype = tmp("_etype");
+  
+  t_field fsize(g_type_i32, size);
+  t_field fktype(g_type_byte, ktype);
+  t_field fvtype(g_type_byte, vtype);
+  t_field fetype(g_type_byte, etype);
+
+  // Declare variables, read header
+  if (ttype->is_map()) {
+    out <<
+      indent() << prefix << " = {}" << endl <<
+      indent() << "(" << ktype << ", " << vtype << ", " << size << " ) = iprot.readMapBegin() " << endl;
+  } else if (ttype->is_set()) {
+    out <<
+      indent() << prefix << " = {}" << endl <<
+      indent() << "(" << etype << ", " << size << ") = iprot.readSetBegin()" << endl;
+  } else if (ttype->is_list()) {
+    out <<
+      indent() << prefix << " = []" << endl <<
+      indent() << "(" << etype << ", " << size << ") = iprot.readListBegin()" << endl;
+  }
+
+  // For loop iterates over elements
+  string i = tmp("_i");
+  indent(out) <<
+    "for " << i << " in (1.." << size << ")" << endl;
+  
+    indent_up();
+    
+    if (ttype->is_map()) {
+      generate_deserialize_map_element(out, (t_map*)ttype, prefix);
+    } else if (ttype->is_set()) {
+      generate_deserialize_set_element(out, (t_set*)ttype, prefix);
+    } else if (ttype->is_list()) {
+      generate_deserialize_list_element(out, (t_list*)ttype, prefix);
+    }
+    
+    indent_down();
+  indent(out) << "end" << endl;
+
+  // Read container end
+  if (ttype->is_map()) {
+    indent(out) << "iprot.readMapEnd()" << endl;
+  } else if (ttype->is_set()) {
+    indent(out) << "iprot.readSetEnd()" << endl;
+  } else if (ttype->is_list()) {
+    indent(out) << "iprot.readListEnd()" << endl;
+  }
+}
+
+
+/**
+ * Generates code to deserialize a map
+ */
+void t_rb_generator::generate_deserialize_map_element(ofstream &out,
+                                                       t_map* tmap,
+                                                       string prefix) {
+  string key = tmp("_key");
+  string val = tmp("_val");
+  t_field fkey(tmap->get_key_type(), key);
+  t_field fval(tmap->get_val_type(), val);
+
+  generate_deserialize_field(out, &fkey);
+  generate_deserialize_field(out, &fval);
+
+  indent(out) <<
+    prefix << "[" << key << "] = " << val << endl;
+}
+
+/**
+ * Write a set element
+ */
+void t_rb_generator::generate_deserialize_set_element(ofstream &out,
+                                                       t_set* tset,
+                                                       string prefix) {
+  string elem = tmp("_elem");
+  t_field felem(tset->get_elem_type(), elem);
+
+  generate_deserialize_field(out, &felem);
+
+  indent(out) <<
+    prefix << "[" << elem << "] = true" << endl;
+}
+
+/**
+ * Write a list element
+ */
+void t_rb_generator::generate_deserialize_list_element(ofstream &out,
+                                                        t_list* tlist,
+                                                        string prefix) {
+  string elem = tmp("_elem");
+  t_field felem(tlist->get_elem_type(), elem);
+
+  generate_deserialize_field(out, &felem);
+
+  indent(out) <<
+    prefix << ".push(" << elem << ")" << endl;
+}
+
+
+/**
+ * Serializes a field of any type.
+ *
+ * @param tfield The field to serialize
+ * @param prefix Name to prepend to field name
+ */
+void t_rb_generator::generate_serialize_field(ofstream &out,
+                                               t_field* tfield,
+                                               string prefix) {
+  t_type* type = tfield->get_type();
+  while (type->is_typedef()) {
+    type = ((t_typedef*)type)->get_type();
+  }
+
+  // Do nothing for void types
+  if (type->is_void()) {
+    throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " +
+      prefix + tfield->get_name();
+  }
+  
+  if (type->is_struct() || type->is_xception()) {
+    generate_serialize_struct(out,
+                              (t_struct*)type,
+                              prefix + tfield->get_name());
+  } else if (type->is_container()) {
+    generate_serialize_container(out,
+                                 type,
+                                 prefix + tfield->get_name());
+  } else if (type->is_base_type() || type->is_enum()) {
+
+    string name = prefix + tfield->get_name();
+
+    indent(out) <<
+      "oprot.";
+    
+    if (type->is_base_type()) {
+      t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
+      switch (tbase) {
+      case t_base_type::TYPE_VOID:
+        throw
+          "compiler error: cannot serialize void field in a struct: " + name;
+        break;
+      case t_base_type::TYPE_STRING:
+        out << "writeString(" << name << ")";
+        break;
+      case t_base_type::TYPE_BOOL:
+        out << "writeBool(" << name << ")";
+        break;
+      case t_base_type::TYPE_BYTE:
+        out << "writeByte(" << name << ")";
+        break;
+      case t_base_type::TYPE_I16:
+        out << "writeI16(" << name << ")";
+        break;
+      case t_base_type::TYPE_I32:
+        out << "writeI32(" << name << ")";
+        break;
+      case t_base_type::TYPE_I64:
+        out << "writeI64(" << name << ")";
+        break;
+      case t_base_type::TYPE_DOUBLE:
+        out << "writeDouble(" << name << ")";
+        break;
+      default:
+        throw "compiler error: no PHP name for base type " + tbase;
+      }
+    } else if (type->is_enum()) {
+      out << "writeI32(" << name << ")";
+    }
+    out << endl;
+  } else {
+    printf("DO NOT KNOW HOW TO SERIALIZE FIELD '%s%s' TYPE '%s'\n",
+           prefix.c_str(),
+           tfield->get_name().c_str(),
+           type->get_name().c_str());
+  }
+}
+
+/**
+ * Serializes all the members of a struct.
+ *
+ * @param tstruct The struct to serialize
+ * @param prefix  String prefix to attach to all fields
+ */
+void t_rb_generator::generate_serialize_struct(ofstream &out,
+                                               t_struct* tstruct,
+                                               string prefix) {
+  indent(out) <<
+    prefix << ".write(oprot)" << endl;
+}
+
+void t_rb_generator::generate_serialize_container(ofstream &out,
+                                                  t_type* ttype,
+                                                  string prefix) {
+  if (ttype->is_map()) {
+    indent(out) <<
+      "oprot.writeMapBegin(" <<
+      type_to_enum(((t_map*)ttype)->get_key_type()) << ", " <<
+      type_to_enum(((t_map*)ttype)->get_val_type()) << ", " <<
+      prefix << ".length)" << endl;
+  } else if (ttype->is_set()) {
+    indent(out) <<
+      "oprot.writeSetBegin(" <<
+      type_to_enum(((t_set*)ttype)->get_elem_type()) << ", " <<
+      prefix << ".length)" << endl;
+  } else if (ttype->is_list()) {
+    indent(out) <<
+      "oprot.writeListBegin(" <<
+      type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " <<
+      prefix << ".length)" << endl;
+  }
+
+  if (ttype->is_map()) {
+    string kiter = tmp("kiter");
+    string viter = tmp("viter");
+    indent(out) << 
+      prefix << ".each do |" << kiter << ", " << viter << "|" << endl;
+    indent_up();
+    generate_serialize_map_element(out, (t_map*)ttype, kiter, viter);
+    indent_down();
+    indent(out) << "end" << endl;
+  } else if (ttype->is_set()) {
+    string iter = tmp("iter");
+    string t = tmp("true");
+    indent(out) << 
+      prefix << ".each do |" << iter << ", " << t << "|" << endl;
+    indent_up();
+    generate_serialize_set_element(out, (t_set*)ttype, iter);
+    indent_down();
+    indent(out) << "end" << endl;
+  } else if (ttype->is_list()) {
+    string iter = tmp("iter");
+    indent(out) << 
+      prefix << ".each do |" << iter << "|" << endl;
+    indent_up();
+    generate_serialize_list_element(out, (t_list*)ttype, iter);
+    indent_down();
+    indent(out) << "end" << endl;
+  }
+    
+  if (ttype->is_map()) {
+    indent(out) <<
+      "oprot.writeMapEnd()" << endl;
+  } else if (ttype->is_set()) {
+    indent(out) <<
+      "oprot.writeSetEnd()" << endl;
+  } else if (ttype->is_list()) {
+    indent(out) <<
+      "oprot.writeListEnd()" << endl;
+  }
+}
+
+/**
+ * Serializes the members of a map.
+ *
+ */
+void t_rb_generator::generate_serialize_map_element(ofstream &out,
+                                                     t_map* tmap,
+                                                     string kiter,
+                                                     string viter) {
+  t_field kfield(tmap->get_key_type(), kiter);
+  generate_serialize_field(out, &kfield, "");
+
+  t_field vfield(tmap->get_val_type(), viter);
+  generate_serialize_field(out, &vfield, "");
+}
+
+/**
+ * Serializes the members of a set.
+ */
+void t_rb_generator::generate_serialize_set_element(ofstream &out,
+                                                     t_set* tset,
+                                                     string iter) {
+  t_field efield(tset->get_elem_type(), iter);
+  generate_serialize_field(out, &efield, "");
+}
+
+/**
+ * Serializes the members of a list.
+ */
+void t_rb_generator::generate_serialize_list_element(ofstream &out,
+                                                      t_list* tlist,
+                                                      string iter) {
+  t_field efield(tlist->get_elem_type(), iter);
+  generate_serialize_field(out, &efield, "");
+}
+
+/**
+ * Declares a field, which may include initialization as necessary.
+ *
+ * @param ttype The type
+ */
+string t_rb_generator::declare_field(t_field* tfield, bool init, bool obj) {
+  string result = "@" + tfield->get_name();
+  if (init) {
+    t_type* type = tfield->get_type();
+    while (type->is_typedef()) {
+      type = ((t_typedef*)type)->get_type();
+    }
+    if (type->is_base_type()) {
+      t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
+      switch (tbase) {
+      case t_base_type::TYPE_VOID:
+        break;
+      case t_base_type::TYPE_STRING:
+        result += " = ''";
+        break;
+      case t_base_type::TYPE_BOOL:
+        result += " = false";
+        break;
+      case t_base_type::TYPE_BYTE:
+      case t_base_type::TYPE_I16:
+      case t_base_type::TYPE_I32:
+      case t_base_type::TYPE_I64:
+        result += " = 0";
+        break;
+      case t_base_type::TYPE_DOUBLE:
+        result += " = 0.0";
+        break;
+      default:
+        throw "compiler error: no PHP initializer for base type " + tbase;
+      }
+    } else if (type->is_enum()) {
+      result += " = 0";
+    } else if (type->is_container()) {
+      if (type->is_map() || type->is_set()) {
+        result += " = {}";
+      } else {
+        result += " = []";
+      }
+    } else if (type->is_struct() || type->is_xception()) {
+      if (obj) {
+        result += " = " + type_name((t_struct*)type) + "()";
+      } else {
+        result += " = nil";
+      }
+    }
+  }
+  return result;
+}
+
+/**
+ * Renders a function signature of the form 'type name(args)'
+ *
+ * @param tfunction Function definition
+ * @return String of rendered function definition
+ */
+string t_rb_generator::function_signature(t_function* tfunction,
+                                           string prefix) {
+  // TODO(mcslee): Nitpicky, no ',' if argument_list is empty
+  return
+    prefix + tfunction->get_name() +
+    "(" +  argument_list(tfunction->get_arglist()) + ")";
+}
+
+/**
+ * Renders a field list
+ */
+string t_rb_generator::argument_list(t_struct* tstruct) {
+  string result = "";
+
+  const vector<t_field*>& fields = tstruct->get_members();
+  vector<t_field*>::const_iterator f_iter;
+  bool first = true;
+  for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) {
+    if (first) {
+      first = false;
+    } else {
+      result += ", ";
+    }
+    result += (*f_iter)->get_name();
+  }
+  return result;
+}
+
+string t_rb_generator::type_name(t_type* ttype) {
+  string prefix = "";
+  t_program* program = ttype->get_program();
+  if (program != NULL && program != program_) {
+    if (!ttype->is_service()) {
+      prefix = program->get_name() + "_types.";
+    }
+  }
+
+  string name = ttype->get_name();
+  if (ttype->is_struct() || ttype->is_xception()) {
+    name = capitalize(ttype->get_name());
+  }
+
+  return prefix + name;
+}
+
+/**
+ * Converts the parse type to a Ruby tyoe
+ */
+string t_rb_generator::type_to_enum(t_type* type) {
+  while (type->is_typedef()) {
+    type = ((t_typedef*)type)->get_type();
+  }
+  
+  if (type->is_base_type()) {
+    t_base_type::t_base tbase = ((t_base_type*)type)->get_base();
+    switch (tbase) {
+    case t_base_type::TYPE_VOID:
+      throw "NO T_VOID CONSTRUCT";
+    case t_base_type::TYPE_STRING:
+      return "TType::STRING";
+    case t_base_type::TYPE_BOOL:
+      return "TType::BOOL";
+    case t_base_type::TYPE_BYTE:
+      return "TType::BYTE";
+    case t_base_type::TYPE_I16:
+      return "TType::I16";
+    case t_base_type::TYPE_I32:
+      return "TType::I32";
+    case t_base_type::TYPE_I64:
+      return "TType::I64";
+    case t_base_type::TYPE_DOUBLE:
+      return "TType::DOUBLE";
+    }
+  } else if (type->is_enum()) {
+    return "TType::I32";
+  } else if (type->is_struct() || type->is_xception()) {
+    return "TType::STRUCT";
+  } else if (type->is_map()) {
+    return "TType::MAP";
+  } else if (type->is_set()) {
+    return "TType::SET";
+  } else if (type->is_list()) {
+    return "TType::LIST";
+  }
+
+  throw "INVALID TYPE IN type_to_enum: " + type->get_name();
+}
diff --git a/compiler/cpp/src/generate/t_rb_generator.h b/compiler/cpp/src/generate/t_rb_generator.h
new file mode 100644
index 0000000..9299f3e
--- /dev/null
+++ b/compiler/cpp/src/generate/t_rb_generator.h
@@ -0,0 +1,148 @@
+#ifndef T_RB_GENERATOR_H
+#define T_RB_GENERATOR_H
+
+#include <string>
+#include <fstream>
+#include <iostream>
+#include <vector>
+
+#include "t_oop_generator.h"
+
+#define T_RB_DIR "gen-rb"
+
+/**
+ * Ruby code generator.
+ *
+ * @author Mark Slee <mcslee@facebook.com>
+ */
+class t_rb_generator : public t_oop_generator {
+ public:
+  t_rb_generator(t_program* program) :
+    t_oop_generator(program) {}
+
+  /**
+   * Init and close methods
+   */
+
+  void init_generator();
+  void close_generator();
+
+  /**
+   * Program-level generation functions
+   */
+
+  void generate_typedef  (t_typedef*  ttypedef);
+  void generate_enum     (t_enum*     tenum);
+  void generate_const    (t_const*    tconst);
+  void generate_struct   (t_struct*   tstruct);
+  void generate_xception (t_struct*   txception);
+  void generate_service  (t_service*  tservice);
+
+  void print_const_value (t_type* type, t_const_value* value);
+
+  /**
+   * Struct generation code
+   */
+
+  void generate_rb_struct(t_struct* tstruct, bool is_exception);
+  void generate_rb_struct_definition(std::ofstream& out, t_struct* tstruct, bool is_xception=false, bool is_result=false);
+  void generate_rb_struct_reader(std::ofstream& out, t_struct* tstruct);
+  void generate_rb_struct_writer(std::ofstream& out, t_struct* tstruct);
+  void generate_rb_function_helpers(t_function* tfunction);
+
+  /**
+   * Service-level generation functions
+   */
+
+  void generate_service_helpers   (t_service*  tservice);
+  void generate_service_interface (t_service* tservice);
+  void generate_service_client    (t_service* tservice);
+  void generate_service_remote    (t_service* tservice);
+  void generate_service_server    (t_service* tservice);
+  void generate_process_function  (t_service* tservice, t_function* tfunction);
+
+  /**
+   * Serialization constructs
+   */
+
+  void generate_deserialize_field        (std::ofstream &out,
+                                          t_field*    tfield, 
+                                          std::string prefix="",
+                                          bool inclass=false);
+  
+  void generate_deserialize_struct       (std::ofstream &out,
+                                          t_struct*   tstruct,
+                                          std::string prefix="");
+  
+  void generate_deserialize_container    (std::ofstream &out,
+                                          t_type*     ttype,
+                                          std::string prefix="");
+  
+  void generate_deserialize_set_element  (std::ofstream &out,
+                                          t_set*      tset,
+                                          std::string prefix="");
+
+  void generate_deserialize_map_element  (std::ofstream &out,
+                                          t_map*      tmap,
+                                          std::string prefix="");
+
+  void generate_deserialize_list_element (std::ofstream &out,
+                                          t_list*     tlist,
+                                          std::string prefix="");
+
+  void generate_serialize_field          (std::ofstream &out,
+                                          t_field*    tfield,
+                                          std::string prefix="");
+
+  void generate_serialize_struct         (std::ofstream &out,
+                                          t_struct*   tstruct,
+                                          std::string prefix="");
+
+  void generate_serialize_container      (std::ofstream &out,
+                                          t_type*     ttype,
+                                          std::string prefix="");
+
+  void generate_serialize_map_element    (std::ofstream &out,
+                                          t_map*      tmap,
+                                          std::string kiter,
+                                          std::string viter);
+
+  void generate_serialize_set_element    (std::ofstream &out,
+                                          t_set*      tmap,
+                                          std::string iter);
+
+  void generate_serialize_list_element   (std::ofstream &out,
+                                          t_list*     tlist,
+                                          std::string iter);
+
+  /**
+   * Helper rendering functions
+   */
+
+  std::string rb_autogen_comment();
+  std::string rb_imports();
+  std::string render_includes();
+  std::string declare_field(t_field* tfield, bool init=false, bool obj=false);
+  std::string type_name(t_type* ttype);
+  std::string function_signature(t_function* tfunction, std::string prefix="");
+  std::string argument_list(t_struct* tstruct);
+  std::string type_to_enum(t_type* ttype);
+
+  std::string capitalize(std::string in) {
+    in[0] = toupper(in[0]);
+    return in;
+  }
+
+ private:
+
+  /**
+   * File streams
+   */
+
+  std::ofstream f_types_;
+  std::ofstream f_consts_; 
+  std::ofstream f_service_;
+
+};
+
+#endif
diff --git a/compiler/cpp/src/main.cc b/compiler/cpp/src/main.cc
index 48552e6..24dca83 100644
--- a/compiler/cpp/src/main.cc
+++ b/compiler/cpp/src/main.cc
@@ -26,6 +26,7 @@
 #include "generate/t_java_generator.h"
 #include "generate/t_php_generator.h"
 #include "generate/t_py_generator.h"
+#include "generate/t_rb_generator.h"
 #include "generate/t_xsd_generator.h"
 
 using namespace std;
@@ -109,6 +110,7 @@
  */
 bool gen_cpp = false;
 bool gen_java = false;
+bool gen_rb = false;
 bool gen_py = false;
 bool gen_xsd = false;
 bool gen_php = false;
@@ -521,6 +523,13 @@
       delete py;
     }
 
+    if (gen_rb) {
+      pverbose("Generating Ruby\n");
+      t_rb_generator* rb = new t_rb_generator(program);
+      rb->generate_program();
+      delete rb;
+    }
+
     if (gen_xsd) {
       pverbose("Generating XSD\n");
       t_xsd_generator* xsd = new t_xsd_generator(program);
@@ -584,6 +593,8 @@
         gen_phpi = true;
       } else if (strcmp(arg, "-py") == 0) {
         gen_py = true;
+      } else if (strcmp(arg, "-rb") == 0) {
+        gen_rb = true;
       } else if (strcmp(arg, "-xsd") == 0) {
         gen_xsd = true;
       } else if (strcmp(arg, "-I") == 0) {
@@ -606,7 +617,7 @@
   }
   
   // You gotta generate something!
-  if (!gen_cpp && !gen_java && !gen_php && !gen_phpi && !gen_py && !gen_xsd) {
+  if (!gen_cpp && !gen_java && !gen_php && !gen_phpi && !gen_py && !gen_rb && !gen_xsd) {
     fprintf(stderr, "!!! No output language(s) specified\n\n");
     usage();
   }
diff --git a/lib/rb/lib/thrift/protocol/tbinaryprotocol.rb b/lib/rb/lib/thrift/protocol/tbinaryprotocol.rb
new file mode 100644
index 0000000..2f0de96
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/tbinaryprotocol.rb
@@ -0,0 +1,173 @@
+require 'thrift/protocol/tprotocol'
+
+class TBinaryProtocol < TProtocol
+  def initialize(trans)
+    super(trans)
+  end
+
+  def writeMessageBegin(name, type, seqid)
+    writeString(name)
+    writeByte(type)
+    writeI32(seqid)
+  end
+
+  def writeFieldBegin(name, type, id)
+    writeByte(type)
+    writeI16(id)
+  end
+  
+  def writeFieldStop()
+    writeByte(TType::STOP)
+  end
+
+  def writeMapBegin(ktype, vtype, size)
+    writeByte(ktype)
+    writeByte(vtype)
+    writeI32(size)
+  end
+  
+  def writeListBegin(etype, size)
+    writeByte(etype)
+    writeI32(size)
+  end
+  
+  def writeSetBegin(etype, size)
+    writeByte(etype)
+    writeI32(size)
+  end
+  
+  def writeBool(bool)
+    if (bool)
+      writeByte(1)
+    else
+      writeByte(0)
+    end
+  end
+
+  def writeByte(byte)
+    trans.write([byte].pack('n')[1..1])
+  end
+
+  def writeI16(i16)
+    trans.write([i16].pack('n'))
+  end
+
+  def writeI32(i32)
+    trans.write([i32].pack('N'))
+  end
+  
+  def writeI64(i64)
+    hi = i64 >> 32
+    lo = i64 & 0xffffffff
+    trans.write([hi, lo].pack('N2'))
+  end
+
+  def writeDouble(dub)
+    trans.write([dub].pack('G'))
+  end
+  
+  def writeString(str)
+    writeI32(str.length)
+    trans.write(str)
+  end
+
+  def readMessageBegin()
+    name = readString()
+    type = readByte()
+    seqid = readI32()
+    return name, type, seqid
+  end
+  
+  def readFieldBegin()
+    type = readByte()
+    if (type === TType::STOP)
+      return nil, type, 0
+    end
+    id = readI16()
+    return nil, type, id
+  end
+  
+  def readMapBegin()
+    ktype = readByte()
+    vtype = readByte()
+    size = readI32()
+    return ktype, vtype, size
+  end
+
+  def readListBegin()
+    etype = readByte()
+    size = readI32()
+    return etype, size
+  end
+
+  def readSetBegin()
+    etype = readByte()
+    size = readI32()
+    return etype, size
+  end  
+  
+  def readBool()
+    byte = readByte()
+    return byte != 0
+  end
+
+  def readByte()
+    dat = trans.readAll(1)
+    val = dat[0]
+    if (val > 0x7f)
+      val = 0 - ((val - 1) ^ 0xff)
+    end
+    return val
+  end
+
+  def readI16()
+    dat = trans.readAll(2)
+    val, = dat.unpack('n')
+    if (val > 0x7fff)
+      val = 0 - ((val - 1) ^ 0xffff)
+    end
+    return val
+  end
+  
+  def readI32()
+    dat = trans.readAll(4)
+    val, = dat.unpack('N')
+    if (val > 0x7fffffff)
+      val = 0 - ((val - 1) ^ 0xffffffff)
+    end
+    return val
+  end
+
+  def readI64()
+    dat = trans.readAll(8)
+    hi, lo = dat.unpack('N2')
+    if (hi > 0x7fffffff)
+      hi = hi ^ 0xffffffff
+      lo = lo ^ 0xffffffff
+      return 0 - hi*4294967296 - lo - 1
+    else
+      return hi*4294967296 + lo
+    end
+  end
+
+  def readDouble()
+    dat = trans.readAll(8)
+    val, = dat.unpack('G')
+    return val
+  end
+
+  def readString()
+    sz = readI32()
+    dat = trans.readAll(sz)
+    return dat
+  end
+
+end
+
+
+class TBinaryProtocolFactory < TProtocolFactory
+  def getProtocol(trans)
+    return TBinaryProtocol.new(trans)
+  end
+end
+
diff --git a/lib/rb/lib/thrift/protocol/tprotocol.rb b/lib/rb/lib/thrift/protocol/tprotocol.rb
new file mode 100644
index 0000000..e3d8787
--- /dev/null
+++ b/lib/rb/lib/thrift/protocol/tprotocol.rb
@@ -0,0 +1,164 @@
+class TType
+  STOP = 0
+  VOID = 1
+  BOOL = 2
+  BYTE = 3
+  DOUBLE = 4
+  I16 = 6
+  I32 = 8
+  I64 = 10
+  STRING = 11
+  STRUCT = 12
+  MAP = 13
+  SET = 14
+  LIST = 15
+end
+
+class TMessageType
+  CALL = 1
+  REPLY = 2
+end
+
+class TProtocol
+  
+  attr_reader :trans
+
+  def initialize(trans)
+    @trans = trans
+  end
+
+  def writeMessageBegin(name, type, seqid); nil; end
+
+  def writeMessageEnd; nil; end
+  
+  def writeStructBegin(name); nil; end
+  
+  def writeStructEnd(); nil; end
+  
+  def writeFieldBegin(name, type, id); nil; end
+  
+  def writeFieldEnd(); nil; end
+  
+  def writeFieldStop(); nil; end
+
+  def writeMapBegin(ktype, vtype, size); nil; end
+  
+  def writeMapEnd(); nil; end
+
+  def writeListBegin(etype, size); nil; end
+  
+  def writeListEnd(); nil; end
+  
+  def writeSetBegin(etype, size); nil; end
+
+  def writeSetEnd(); nil; end
+  
+  def writeBool(bool); nil; end
+  
+  def writeByte(byte); nil; end
+  
+  def writeI16(i16); nil; end
+  
+  def writeI32(i32); nil; end
+  
+  def writeI64(i64); nil; end
+  
+  def writeDouble(dub); nil; end
+  
+  def writeString(str); nil; end
+  
+  def readMessageBegin(); nil; end
+  
+  def readMessageEnd(); nil; end
+  
+  def readStructBegin(); nil; end
+  
+  def readStructEnd(); nil; end
+  
+  def readFieldBegin(); nil; end
+  
+  def readFieldEnd(); nil; end
+  
+  def readMapBegin(); nil; end
+  
+  def readMapEnd(); nil; end
+  
+  def readListBegin(); nil; end
+  
+  def readListEnd(); nil; end
+
+  def readSetBegin(); nil; end
+  
+  def readSetEnd(); nil; end
+  
+  def readBool(); nil; end
+  
+  def readByte(); nil; end
+
+  def readI16(); nil; end
+  
+  def readI32(); nil; end
+  
+  def readI64(); nil; end
+  
+  def readDouble(); nil; end
+  
+  def readString(); nil; end
+  
+  def skip(type)
+    if type === TType::STOP
+      nil
+    elsif type === TType::BOOL
+      readBool()
+    elsif type === TType::BYTE
+      readByte()
+    elsif type === TType::I16
+      readI16()
+    elsif type === TType::I32
+      readI32()
+    elsif type === TType::I64
+      readI64()
+    elsif type === TType::DOUBLE
+      readDouble()
+    elsif type === TType::STRING
+      readString()
+    elsif type === TType::STRUCT
+      readStructBegin()
+      while true
+        name, type, id = readFieldBegin()
+        if type === TType::STOP
+          break
+        else
+          skip(type)
+          readFieldEnd()
+        end
+        readStructEnd()
+      end
+    elsif type === TType::MAP
+      ktype, vtype, size = readMapBegin()
+      for i in 1..size
+        skip(ktype)
+        skip(vtype)
+      end
+      readMapEnd()
+    elsif type === TType::SET
+      etype, size = readSetBegin()
+      for i in 1..size
+        skip(etype)
+      end
+      readSetEnd()
+    elsif type === TType::LIST
+      etype, size = readListBegin()
+      for i in 1..size
+        skip(etype)
+      end
+      readListEnd()
+    end
+  end
+
+end
+
+class TProtocolFactory
+  def getProtocol(trans); nil end
+end
+
diff --git a/lib/rb/lib/thrift/thrift.rb b/lib/rb/lib/thrift/thrift.rb
new file mode 100644
index 0000000..347079f
--- /dev/null
+++ b/lib/rb/lib/thrift/thrift.rb
@@ -0,0 +1,3 @@
+module TProcessor
+  def process(iprot, oprot); nil; end
+end
diff --git a/lib/rb/lib/thrift/transport/tsocket.rb b/lib/rb/lib/thrift/transport/tsocket.rb
new file mode 100644
index 0000000..232f01e
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/tsocket.rb
@@ -0,0 +1,54 @@
+require 'thrift/transport/ttransport'
+require 'socket'
+
+class TSocket < TTransport
+  def initialize(host, port)
+    @host = host
+    @port = port
+    @handle = nil
+  end
+
+  def open()
+    @handle = TCPSocket.new(@host, @port)
+  end
+
+  def isOpen()
+    return @handle != nil
+  end
+  
+  def write(str)
+    @handle.write(str)
+  end
+
+  def read(sz)
+    return @handle.recv(sz)
+  end
+
+  def close()
+    @handle.close() unless @handle.nil?
+  end
+    
+end
+
+class TServerSocket < TServerTransport
+  def initialize(port)
+    @port = port
+    @handle = nil
+  end
+
+  def listen()
+    @handle = TCPserver.new(nil, @port)
+  end
+
+  def accept()
+    if (@handle != nil)
+      return @handle.accept()
+    end
+    return nil
+  end
+   
+  def close()
+   @handle.close() unless @handle.nil?
+  end
+
+end
diff --git a/lib/rb/lib/thrift/transport/ttransport.rb b/lib/rb/lib/thrift/transport/ttransport.rb
new file mode 100644
index 0000000..e0369d1
--- /dev/null
+++ b/lib/rb/lib/thrift/transport/ttransport.rb
@@ -0,0 +1,42 @@
+class TTransport
+  def isOpen(); nil; end
+  
+  def open(); nil; end
+  
+  def close(); nil; end
+  
+  def read(sz); nil; end
+  
+  def readAll(sz)
+    buff = ''
+    have = 0
+    while (have < sz)
+      chunk = read(sz - have)
+      have += chunk.length
+      buff += chunk
+    end
+    return buff
+  end
+
+  def write(buf); nil; end
+
+  def flush(); nil; end
+  
+end
+
+class TServerTransport
+  def listen(); nil; end
+  
+  def accept(); nil; end
+
+  def close(); nil; end
+
+end
+
+class TTransportFactory
+  def getTransport(trans)
+    return trans
+  end
+end
+
+    
diff --git a/lib/rb/setup.rb b/lib/rb/setup.rb
new file mode 100644
index 0000000..424a5f3
--- /dev/null
+++ b/lib/rb/setup.rb
@@ -0,0 +1,1585 @@
+#
+# setup.rb
+#
+# Copyright (c) 2000-2005 Minero Aoki
+#
+# This program is free software.
+# You can distribute/modify this program under the terms of
+# the GNU LGPL, Lesser General Public License version 2.1.
+#
+
+unless Enumerable.method_defined?(:map)   # Ruby 1.4.6
+  module Enumerable
+    alias map collect
+  end
+end
+
+unless File.respond_to?(:read)   # Ruby 1.6
+  def File.read(fname)
+    open(fname) {|f|
+      return f.read
+    }
+  end
+end
+
+unless Errno.const_defined?(:ENOTEMPTY)   # Windows?
+  module Errno
+    class ENOTEMPTY
+      # We do not raise this exception, implementation is not needed.
+    end
+  end
+end
+
+def File.binread(fname)
+  open(fname, 'rb') {|f|
+    return f.read
+  }
+end
+
+# for corrupted Windows' stat(2)
+def File.dir?(path)
+  File.directory?((path[-1,1] == '/') ? path : path + '/')
+end
+
+
+class ConfigTable
+
+  include Enumerable
+
+  def initialize(rbconfig)
+    @rbconfig = rbconfig
+    @items = []
+    @table = {}
+    # options
+    @install_prefix = nil
+    @config_opt = nil
+    @verbose = true
+    @no_harm = false
+  end
+
+  attr_accessor :install_prefix
+  attr_accessor :config_opt
+
+  attr_writer :verbose
+
+  def verbose?
+    @verbose
+  end
+
+  attr_writer :no_harm
+
+  def no_harm?
+    @no_harm
+  end
+
+  def [](key)
+    lookup(key).resolve(self)
+  end
+
+  def []=(key, val)
+    lookup(key).set val
+  end
+
+  def names
+    @items.map {|i| i.name }
+  end
+
+  def each(&block)
+    @items.each(&block)
+  end
+
+  def key?(name)
+    @table.key?(name)
+  end
+
+  def lookup(name)
+    @table[name] or setup_rb_error "no such config item: #{name}"
+  end
+
+  def add(item)
+    @items.push item
+    @table[item.name] = item
+  end
+
+  def remove(name)
+    item = lookup(name)
+    @items.delete_if {|i| i.name == name }
+    @table.delete_if {|name, i| i.name == name }
+    item
+  end
+
+  def load_script(path, inst = nil)
+    if File.file?(path)
+      MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path
+    end
+  end
+
+  def savefile
+    '.config'
+  end
+
+  def load_savefile
+    begin
+      File.foreach(savefile()) do |line|
+        k, v = *line.split(/=/, 2)
+        self[k] = v.strip
+      end
+    rescue Errno::ENOENT
+      setup_rb_error $!.message + "\n#{File.basename($0)} config first"
+    end
+  end
+
+  def save
+    @items.each {|i| i.value }
+    File.open(savefile(), 'w') {|f|
+      @items.each do |i|
+        f.printf "%s=%s\n", i.name, i.value if i.value? and i.value
+      end
+    }
+  end
+
+  def load_standard_entries
+    standard_entries(@rbconfig).each do |ent|
+      add ent
+    end
+  end
+
+  def standard_entries(rbconfig)
+    c = rbconfig
+
+    rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT'])
+
+    major = c['MAJOR'].to_i
+    minor = c['MINOR'].to_i
+    teeny = c['TEENY'].to_i
+    version = "#{major}.#{minor}"
+
+    # ruby ver. >= 1.4.4?
+    newpath_p = ((major >= 2) or
+                 ((major == 1) and
+                  ((minor >= 5) or
+                   ((minor == 4) and (teeny >= 4)))))
+
+    if c['rubylibdir']
+      # V > 1.6.3
+      libruby         = "#{c['prefix']}/lib/ruby"
+      librubyver      = c['rubylibdir']
+      librubyverarch  = c['archdir']
+      siteruby        = c['sitedir']
+      siterubyver     = c['sitelibdir']
+      siterubyverarch = c['sitearchdir']
+    elsif newpath_p
+      # 1.4.4 <= V <= 1.6.3
+      libruby         = "#{c['prefix']}/lib/ruby"
+      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
+      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+      siteruby        = c['sitedir']
+      siterubyver     = "$siteruby/#{version}"
+      siterubyverarch = "$siterubyver/#{c['arch']}"
+    else
+      # V < 1.4.4
+      libruby         = "#{c['prefix']}/lib/ruby"
+      librubyver      = "#{c['prefix']}/lib/ruby/#{version}"
+      librubyverarch  = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}"
+      siteruby        = "#{c['prefix']}/lib/ruby/#{version}/site_ruby"
+      siterubyver     = siteruby
+      siterubyverarch = "$siterubyver/#{c['arch']}"
+    end
+    parameterize = lambda {|path|
+      path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix')
+    }
+
+    if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg }
+      makeprog = arg.sub(/'/, '').split(/=/, 2)[1]
+    else
+      makeprog = 'make'
+    end
+
+    [
+      ExecItem.new('installdirs', 'std/site/home',
+                   'std: install under libruby; site: install under site_ruby; home: install under $HOME')\
+          {|val, table|
+            case val
+            when 'std'
+              table['rbdir'] = '$librubyver'
+              table['sodir'] = '$librubyverarch'
+            when 'site'
+              table['rbdir'] = '$siterubyver'
+              table['sodir'] = '$siterubyverarch'
+            when 'home'
+              setup_rb_error '$HOME was not set' unless ENV['HOME']
+              table['prefix'] = ENV['HOME']
+              table['rbdir'] = '$libdir/ruby'
+              table['sodir'] = '$libdir/ruby'
+            end
+          },
+      PathItem.new('prefix', 'path', c['prefix'],
+                   'path prefix of target environment'),
+      PathItem.new('bindir', 'path', parameterize.call(c['bindir']),
+                   'the directory for commands'),
+      PathItem.new('libdir', 'path', parameterize.call(c['libdir']),
+                   'the directory for libraries'),
+      PathItem.new('datadir', 'path', parameterize.call(c['datadir']),
+                   'the directory for shared data'),
+      PathItem.new('mandir', 'path', parameterize.call(c['mandir']),
+                   'the directory for man pages'),
+      PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']),
+                   'the directory for system configuration files'),
+      PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']),
+                   'the directory for local state data'),
+      PathItem.new('libruby', 'path', libruby,
+                   'the directory for ruby libraries'),
+      PathItem.new('librubyver', 'path', librubyver,
+                   'the directory for standard ruby libraries'),
+      PathItem.new('librubyverarch', 'path', librubyverarch,
+                   'the directory for standard ruby extensions'),
+      PathItem.new('siteruby', 'path', siteruby,
+          'the directory for version-independent aux ruby libraries'),
+      PathItem.new('siterubyver', 'path', siterubyver,
+                   'the directory for aux ruby libraries'),
+      PathItem.new('siterubyverarch', 'path', siterubyverarch,
+                   'the directory for aux ruby binaries'),
+      PathItem.new('rbdir', 'path', '$siterubyver',
+                   'the directory for ruby scripts'),
+      PathItem.new('sodir', 'path', '$siterubyverarch',
+                   'the directory for ruby extentions'),
+      PathItem.new('rubypath', 'path', rubypath,
+                   'the path to set to #! line'),
+      ProgramItem.new('rubyprog', 'name', rubypath,
+                      'the ruby program using for installation'),
+      ProgramItem.new('makeprog', 'name', makeprog,
+                      'the make program to compile ruby extentions'),
+      SelectItem.new('shebang', 'all/ruby/never', 'ruby',
+                     'shebang line (#!) editing mode'),
+      BoolItem.new('without-ext', 'yes/no', 'no',
+                   'does not compile/install ruby extentions')
+    ]
+  end
+  private :standard_entries
+
+  def load_multipackage_entries
+    multipackage_entries().each do |ent|
+      add ent
+    end
+  end
+
+  def multipackage_entries
+    [
+      PackageSelectionItem.new('with', 'name,name...', '', 'ALL',
+                               'package names that you want to install'),
+      PackageSelectionItem.new('without', 'name,name...', '', 'NONE',
+                               'package names that you do not want to install')
+    ]
+  end
+  private :multipackage_entries
+
+  ALIASES = {
+    'std-ruby'         => 'librubyver',
+    'stdruby'          => 'librubyver',
+    'rubylibdir'       => 'librubyver',
+    'archdir'          => 'librubyverarch',
+    'site-ruby-common' => 'siteruby',     # For backward compatibility
+    'site-ruby'        => 'siterubyver',  # For backward compatibility
+    'bin-dir'          => 'bindir',
+    'bin-dir'          => 'bindir',
+    'rb-dir'           => 'rbdir',
+    'so-dir'           => 'sodir',
+    'data-dir'         => 'datadir',
+    'ruby-path'        => 'rubypath',
+    'ruby-prog'        => 'rubyprog',
+    'ruby'             => 'rubyprog',
+    'make-prog'        => 'makeprog',
+    'make'             => 'makeprog'
+  }
+
+  def fixup
+    ALIASES.each do |ali, name|
+      @table[ali] = @table[name]
+    end
+    @items.freeze
+    @table.freeze
+    @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/
+  end
+
+  def parse_opt(opt)
+    m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}"
+    m.to_a[1,2]
+  end
+
+  def dllext
+    @rbconfig['DLEXT']
+  end
+
+  def value_config?(name)
+    lookup(name).value?
+  end
+
+  class Item
+    def initialize(name, template, default, desc)
+      @name = name.freeze
+      @template = template
+      @value = default
+      @default = default
+      @description = desc
+    end
+
+    attr_reader :name
+    attr_reader :description
+
+    attr_accessor :default
+    alias help_default default
+
+    def help_opt
+      "--#{@name}=#{@template}"
+    end
+
+    def value?
+      true
+    end
+
+    def value
+      @value
+    end
+
+    def resolve(table)
+      @value.gsub(%r<\$([^/]+)>) { table[$1] }
+    end
+
+    def set(val)
+      @value = check(val)
+    end
+
+    private
+
+    def check(val)
+      setup_rb_error "config: --#{name} requires argument" unless val
+      val
+    end
+  end
+
+  class BoolItem < Item
+    def config_type
+      'bool'
+    end
+
+    def help_opt
+      "--#{@name}"
+    end
+
+    private
+
+    def check(val)
+      return 'yes' unless val
+      case val
+      when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes'
+      when /\An(o)?\z/i, /\Af(alse)\z/i  then 'no'
+      else
+        setup_rb_error "config: --#{@name} accepts only yes/no for argument"
+      end
+    end
+  end
+
+  class PathItem < Item
+    def config_type
+      'path'
+    end
+
+    private
+
+    def check(path)
+      setup_rb_error "config: --#{@name} requires argument"  unless path
+      path[0,1] == '$' ? path : File.expand_path(path)
+    end
+  end
+
+  class ProgramItem < Item
+    def config_type
+      'program'
+    end
+  end
+
+  class SelectItem < Item
+    def initialize(name, selection, default, desc)
+      super
+      @ok = selection.split('/')
+    end
+
+    def config_type
+      'select'
+    end
+
+    private
+
+    def check(val)
+      unless @ok.include?(val.strip)
+        setup_rb_error "config: use --#{@name}=#{@template} (#{val})"
+      end
+      val.strip
+    end
+  end
+
+  class ExecItem < Item
+    def initialize(name, selection, desc, &block)
+      super name, selection, nil, desc
+      @ok = selection.split('/')
+      @action = block
+    end
+
+    def config_type
+      'exec'
+    end
+
+    def value?
+      false
+    end
+
+    def resolve(table)
+      setup_rb_error "$#{name()} wrongly used as option value"
+    end
+
+    undef set
+
+    def evaluate(val, table)
+      v = val.strip.downcase
+      unless @ok.include?(v)
+        setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})"
+      end
+      @action.call v, table
+    end
+  end
+
+  class PackageSelectionItem < Item
+    def initialize(name, template, default, help_default, desc)
+      super name, template, default, desc
+      @help_default = help_default
+    end
+
+    attr_reader :help_default
+
+    def config_type
+      'package'
+    end
+
+    private
+
+    def check(val)
+      unless File.dir?("packages/#{val}")
+        setup_rb_error "config: no such package: #{val}"
+      end
+      val
+    end
+  end
+
+  class MetaConfigEnvironment
+    def initialize(config, installer)
+      @config = config
+      @installer = installer
+    end
+
+    def config_names
+      @config.names
+    end
+
+    def config?(name)
+      @config.key?(name)
+    end
+
+    def bool_config?(name)
+      @config.lookup(name).config_type == 'bool'
+    end
+
+    def path_config?(name)
+      @config.lookup(name).config_type == 'path'
+    end
+
+    def value_config?(name)
+      @config.lookup(name).config_type != 'exec'
+    end
+
+    def add_config(item)
+      @config.add item
+    end
+
+    def add_bool_config(name, default, desc)
+      @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc)
+    end
+
+    def add_path_config(name, default, desc)
+      @config.add PathItem.new(name, 'path', default, desc)
+    end
+
+    def set_config_default(name, default)
+      @config.lookup(name).default = default
+    end
+
+    def remove_config(name)
+      @config.remove(name)
+    end
+
+    # For only multipackage
+    def packages
+      raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer
+      @installer.packages
+    end
+
+    # For only multipackage
+    def declare_packages(list)
+      raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer
+      @installer.packages = list
+    end
+  end
+
+end   # class ConfigTable
+
+
+# This module requires: #verbose?, #no_harm?
+module FileOperations
+
+  def mkdir_p(dirname, prefix = nil)
+    dirname = prefix + File.expand_path(dirname) if prefix
+    $stderr.puts "mkdir -p #{dirname}" if verbose?
+    return if no_harm?
+
+    # Does not check '/', it's too abnormal.
+    dirs = File.expand_path(dirname).split(%r<(?=/)>)
+    if /\A[a-z]:\z/i =~ dirs[0]
+      disk = dirs.shift
+      dirs[0] = disk + dirs[0]
+    end
+    dirs.each_index do |idx|
+      path = dirs[0..idx].join('')
+      Dir.mkdir path unless File.dir?(path)
+    end
+  end
+
+  def rm_f(path)
+    $stderr.puts "rm -f #{path}" if verbose?
+    return if no_harm?
+    force_remove_file path
+  end
+
+  def rm_rf(path)
+    $stderr.puts "rm -rf #{path}" if verbose?
+    return if no_harm?
+    remove_tree path
+  end
+
+  def remove_tree(path)
+    if File.symlink?(path)
+      remove_file path
+    elsif File.dir?(path)
+      remove_tree0 path
+    else
+      force_remove_file path
+    end
+  end
+
+  def remove_tree0(path)
+    Dir.foreach(path) do |ent|
+      next if ent == '.'
+      next if ent == '..'
+      entpath = "#{path}/#{ent}"
+      if File.symlink?(entpath)
+        remove_file entpath
+      elsif File.dir?(entpath)
+        remove_tree0 entpath
+      else
+        force_remove_file entpath
+      end
+    end
+    begin
+      Dir.rmdir path
+    rescue Errno::ENOTEMPTY
+      # directory may not be empty
+    end
+  end
+
+  def move_file(src, dest)
+    force_remove_file dest
+    begin
+      File.rename src, dest
+    rescue
+      File.open(dest, 'wb') {|f|
+        f.write File.binread(src)
+      }
+      File.chmod File.stat(src).mode, dest
+      File.unlink src
+    end
+  end
+
+  def force_remove_file(path)
+    begin
+      remove_file path
+    rescue
+    end
+  end
+
+  def remove_file(path)
+    File.chmod 0777, path
+    File.unlink path
+  end
+
+  def install(from, dest, mode, prefix = nil)
+    $stderr.puts "install #{from} #{dest}" if verbose?
+    return if no_harm?
+
+    realdest = prefix ? prefix + File.expand_path(dest) : dest
+    realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest)
+    str = File.binread(from)
+    if diff?(str, realdest)
+      verbose_off {
+        rm_f realdest if File.exist?(realdest)
+      }
+      File.open(realdest, 'wb') {|f|
+        f.write str
+      }
+      File.chmod mode, realdest
+
+      File.open("#{objdir_root()}/InstalledFiles", 'a') {|f|
+        if prefix
+          f.puts realdest.sub(prefix, '')
+        else
+          f.puts realdest
+        end
+      }
+    end
+  end
+
+  def diff?(new_content, path)
+    return true unless File.exist?(path)
+    new_content != File.binread(path)
+  end
+
+  def command(*args)
+    $stderr.puts args.join(' ') if verbose?
+    system(*args) or raise RuntimeError,
+        "system(#{args.map{|a| a.inspect }.join(' ')}) failed"
+  end
+
+  def ruby(*args)
+    command config('rubyprog'), *args
+  end
+  
+  def make(task = nil)
+    command(*[config('makeprog'), task].compact)
+  end
+
+  def extdir?(dir)
+    File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb")
+  end
+
+  def files_of(dir)
+    Dir.open(dir) {|d|
+      return d.select {|ent| File.file?("#{dir}/#{ent}") }
+    }
+  end
+
+  DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn )
+
+  def directories_of(dir)
+    Dir.open(dir) {|d|
+      return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT
+    }
+  end
+
+end
+
+
+# This module requires: #srcdir_root, #objdir_root, #relpath
+module HookScriptAPI
+
+  def get_config(key)
+    @config[key]
+  end
+
+  alias config get_config
+
+  # obsolete: use metaconfig to change configuration
+  def set_config(key, val)
+    @config[key] = val
+  end
+
+  #
+  # srcdir/objdir (works only in the package directory)
+  #
+
+  def curr_srcdir
+    "#{srcdir_root()}/#{relpath()}"
+  end
+
+  def curr_objdir
+    "#{objdir_root()}/#{relpath()}"
+  end
+
+  def srcfile(path)
+    "#{curr_srcdir()}/#{path}"
+  end
+
+  def srcexist?(path)
+    File.exist?(srcfile(path))
+  end
+
+  def srcdirectory?(path)
+    File.dir?(srcfile(path))
+  end
+  
+  def srcfile?(path)
+    File.file?(srcfile(path))
+  end
+
+  def srcentries(path = '.')
+    Dir.open("#{curr_srcdir()}/#{path}") {|d|
+      return d.to_a - %w(. ..)
+    }
+  end
+
+  def srcfiles(path = '.')
+    srcentries(path).select {|fname|
+      File.file?(File.join(curr_srcdir(), path, fname))
+    }
+  end
+
+  def srcdirectories(path = '.')
+    srcentries(path).select {|fname|
+      File.dir?(File.join(curr_srcdir(), path, fname))
+    }
+  end
+
+end
+
+
+class ToplevelInstaller
+
+  Version   = '3.4.1'
+  Copyright = 'Copyright (c) 2000-2005 Minero Aoki'
+
+  TASKS = [
+    [ 'all',      'do config, setup, then install' ],
+    [ 'config',   'saves your configurations' ],
+    [ 'show',     'shows current configuration' ],
+    [ 'setup',    'compiles ruby extentions and others' ],
+    [ 'install',  'installs files' ],
+    [ 'test',     'run all tests in test/' ],
+    [ 'clean',    "does `make clean' for each extention" ],
+    [ 'distclean',"does `make distclean' for each extention" ]
+  ]
+
+  def ToplevelInstaller.invoke
+    config = ConfigTable.new(load_rbconfig())
+    config.load_standard_entries
+    config.load_multipackage_entries if multipackage?
+    config.fixup
+    klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller)
+    klass.new(File.dirname($0), config).invoke
+  end
+
+  def ToplevelInstaller.multipackage?
+    File.dir?(File.dirname($0) + '/packages')
+  end
+
+  def ToplevelInstaller.load_rbconfig
+    if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg }
+      ARGV.delete(arg)
+      load File.expand_path(arg.split(/=/, 2)[1])
+      $".push 'rbconfig.rb'
+    else
+      require 'rbconfig'
+    end
+    ::Config::CONFIG
+  end
+
+  def initialize(ardir_root, config)
+    @ardir = File.expand_path(ardir_root)
+    @config = config
+    # cache
+    @valid_task_re = nil
+  end
+
+  def config(key)
+    @config[key]
+  end
+
+  def inspect
+    "#<#{self.class} #{__id__()}>"
+  end
+
+  def invoke
+    run_metaconfigs
+    case task = parsearg_global()
+    when nil, 'all'
+      parsearg_config
+      init_installers
+      exec_config
+      exec_setup
+      exec_install
+    else
+      case task
+      when 'config', 'test'
+        ;
+      when 'clean', 'distclean'
+        @config.load_savefile if File.exist?(@config.savefile)
+      else
+        @config.load_savefile
+      end
+      __send__ "parsearg_#{task}"
+      init_installers
+      __send__ "exec_#{task}"
+    end
+  end
+  
+  def run_metaconfigs
+    @config.load_script "#{@ardir}/metaconfig"
+  end
+
+  def init_installers
+    @installer = Installer.new(@config, @ardir, File.expand_path('.'))
+  end
+
+  #
+  # Hook Script API bases
+  #
+
+  def srcdir_root
+    @ardir
+  end
+
+  def objdir_root
+    '.'
+  end
+
+  def relpath
+    '.'
+  end
+
+  #
+  # Option Parsing
+  #
+
+  def parsearg_global
+    while arg = ARGV.shift
+      case arg
+      when /\A\w+\z/
+        setup_rb_error "invalid task: #{arg}" unless valid_task?(arg)
+        return arg
+      when '-q', '--quiet'
+        @config.verbose = false
+      when '--verbose'
+        @config.verbose = true
+      when '--help'
+        print_usage $stdout
+        exit 0
+      when '--version'
+        puts "#{File.basename($0)} version #{Version}"
+        exit 0
+      when '--copyright'
+        puts Copyright
+        exit 0
+      else
+        setup_rb_error "unknown global option '#{arg}'"
+      end
+    end
+    nil
+  end
+
+  def valid_task?(t)
+    valid_task_re() =~ t
+  end
+
+  def valid_task_re
+    @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/
+  end
+
+  def parsearg_no_options
+    unless ARGV.empty?
+      task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1)
+      setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}"
+    end
+  end
+
+  alias parsearg_show       parsearg_no_options
+  alias parsearg_setup      parsearg_no_options
+  alias parsearg_test       parsearg_no_options
+  alias parsearg_clean      parsearg_no_options
+  alias parsearg_distclean  parsearg_no_options
+
+  def parsearg_config
+    evalopt = []
+    set = []
+    @config.config_opt = []
+    while i = ARGV.shift
+      if /\A--?\z/ =~ i
+        @config.config_opt = ARGV.dup
+        break
+      end
+      name, value = *@config.parse_opt(i)
+      if @config.value_config?(name)
+        @config[name] = value
+      else
+        evalopt.push [name, value]
+      end
+      set.push name
+    end
+    evalopt.each do |name, value|
+      @config.lookup(name).evaluate value, @config
+    end
+    # Check if configuration is valid
+    set.each do |n|
+      @config[n] if @config.value_config?(n)
+    end
+  end
+
+  def parsearg_install
+    @config.no_harm = false
+    @config.install_prefix = ''
+    while a = ARGV.shift
+      case a
+      when '--no-harm'
+        @config.no_harm = true
+      when /\A--prefix=/
+        path = a.split(/=/, 2)[1]
+        path = File.expand_path(path) unless path[0,1] == '/'
+        @config.install_prefix = path
+      else
+        setup_rb_error "install: unknown option #{a}"
+      end
+    end
+  end
+
+  def print_usage(out)
+    out.puts 'Typical Installation Procedure:'
+    out.puts "  $ ruby #{File.basename $0} config"
+    out.puts "  $ ruby #{File.basename $0} setup"
+    out.puts "  # ruby #{File.basename $0} install (may require root privilege)"
+    out.puts
+    out.puts 'Detailed Usage:'
+    out.puts "  ruby #{File.basename $0} <global option>"
+    out.puts "  ruby #{File.basename $0} [<global options>] <task> [<task options>]"
+
+    fmt = "  %-24s %s\n"
+    out.puts
+    out.puts 'Global options:'
+    out.printf fmt, '-q,--quiet',   'suppress message outputs'
+    out.printf fmt, '   --verbose', 'output messages verbosely'
+    out.printf fmt, '   --help',    'print this message'
+    out.printf fmt, '   --version', 'print version and quit'
+    out.printf fmt, '   --copyright',  'print copyright and quit'
+    out.puts
+    out.puts 'Tasks:'
+    TASKS.each do |name, desc|
+      out.printf fmt, name, desc
+    end
+
+    fmt = "  %-24s %s [%s]\n"
+    out.puts
+    out.puts 'Options for CONFIG or ALL:'
+    @config.each do |item|
+      out.printf fmt, item.help_opt, item.description, item.help_default
+    end
+    out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's"
+    out.puts
+    out.puts 'Options for INSTALL:'
+    out.printf fmt, '--no-harm', 'only display what to do if given', 'off'
+    out.printf fmt, '--prefix=path',  'install path prefix', ''
+    out.puts
+  end
+
+  #
+  # Task Handlers
+  #
+
+  def exec_config
+    @installer.exec_config
+    @config.save   # must be final
+  end
+
+  def exec_setup
+    @installer.exec_setup
+  end
+
+  def exec_install
+    @installer.exec_install
+  end
+
+  def exec_test
+    @installer.exec_test
+  end
+
+  def exec_show
+    @config.each do |i|
+      printf "%-20s %s\n", i.name, i.value if i.value?
+    end
+  end
+
+  def exec_clean
+    @installer.exec_clean
+  end
+
+  def exec_distclean
+    @installer.exec_distclean
+  end
+
+end   # class ToplevelInstaller
+
+
+class ToplevelInstallerMulti < ToplevelInstaller
+
+  include FileOperations
+
+  def initialize(ardir_root, config)
+    super
+    @packages = directories_of("#{@ardir}/packages")
+    raise 'no package exists' if @packages.empty?
+    @root_installer = Installer.new(@config, @ardir, File.expand_path('.'))
+  end
+
+  def run_metaconfigs
+    @config.load_script "#{@ardir}/metaconfig", self
+    @packages.each do |name|
+      @config.load_script "#{@ardir}/packages/#{name}/metaconfig"
+    end
+  end
+
+  attr_reader :packages
+
+  def packages=(list)
+    raise 'package list is empty' if list.empty?
+    list.each do |name|
+      raise "directory packages/#{name} does not exist"\
+              unless File.dir?("#{@ardir}/packages/#{name}")
+    end
+    @packages = list
+  end
+
+  def init_installers
+    @installers = {}
+    @packages.each do |pack|
+      @installers[pack] = Installer.new(@config,
+                                       "#{@ardir}/packages/#{pack}",
+                                       "packages/#{pack}")
+    end
+    with    = extract_selection(config('with'))
+    without = extract_selection(config('without'))
+    @selected = @installers.keys.select {|name|
+                  (with.empty? or with.include?(name)) \
+                      and not without.include?(name)
+                }
+  end
+
+  def extract_selection(list)
+    a = list.split(/,/)
+    a.each do |name|
+      setup_rb_error "no such package: #{name}"  unless @installers.key?(name)
+    end
+    a
+  end
+
+  def print_usage(f)
+    super
+    f.puts 'Inluded packages:'
+    f.puts '  ' + @packages.sort.join(' ')
+    f.puts
+  end
+
+  #
+  # Task Handlers
+  #
+
+  def exec_config
+    run_hook 'pre-config'
+    each_selected_installers {|inst| inst.exec_config }
+    run_hook 'post-config'
+    @config.save   # must be final
+  end
+
+  def exec_setup
+    run_hook 'pre-setup'
+    each_selected_installers {|inst| inst.exec_setup }
+    run_hook 'post-setup'
+  end
+
+  def exec_install
+    run_hook 'pre-install'
+    each_selected_installers {|inst| inst.exec_install }
+    run_hook 'post-install'
+  end
+
+  def exec_test
+    run_hook 'pre-test'
+    each_selected_installers {|inst| inst.exec_test }
+    run_hook 'post-test'
+  end
+
+  def exec_clean
+    rm_f @config.savefile
+    run_hook 'pre-clean'
+    each_selected_installers {|inst| inst.exec_clean }
+    run_hook 'post-clean'
+  end
+
+  def exec_distclean
+    rm_f @config.savefile
+    run_hook 'pre-distclean'
+    each_selected_installers {|inst| inst.exec_distclean }
+    run_hook 'post-distclean'
+  end
+
+  #
+  # lib
+  #
+
+  def each_selected_installers
+    Dir.mkdir 'packages' unless File.dir?('packages')
+    @selected.each do |pack|
+      $stderr.puts "Processing the package `#{pack}' ..." if verbose?
+      Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}")
+      Dir.chdir "packages/#{pack}"
+      yield @installers[pack]
+      Dir.chdir '../..'
+    end
+  end
+
+  def run_hook(id)
+    @root_installer.run_hook id
+  end
+
+  # module FileOperations requires this
+  def verbose?
+    @config.verbose?
+  end
+
+  # module FileOperations requires this
+  def no_harm?
+    @config.no_harm?
+  end
+
+end   # class ToplevelInstallerMulti
+
+
+class Installer
+
+  FILETYPES = %w( bin lib ext data conf man )
+
+  include FileOperations
+  include HookScriptAPI
+
+  def initialize(config, srcroot, objroot)
+    @config = config
+    @srcdir = File.expand_path(srcroot)
+    @objdir = File.expand_path(objroot)
+    @currdir = '.'
+  end
+
+  def inspect
+    "#<#{self.class} #{File.basename(@srcdir)}>"
+  end
+
+  def noop(rel)
+  end
+
+  #
+  # Hook Script API base methods
+  #
+
+  def srcdir_root
+    @srcdir
+  end
+
+  def objdir_root
+    @objdir
+  end
+
+  def relpath
+    @currdir
+  end
+
+  #
+  # Config Access
+  #
+
+  # module FileOperations requires this
+  def verbose?
+    @config.verbose?
+  end
+
+  # module FileOperations requires this
+  def no_harm?
+    @config.no_harm?
+  end
+
+  def verbose_off
+    begin
+      save, @config.verbose = @config.verbose?, false
+      yield
+    ensure
+      @config.verbose = save
+    end
+  end
+
+  #
+  # TASK config
+  #
+
+  def exec_config
+    exec_task_traverse 'config'
+  end
+
+  alias config_dir_bin noop
+  alias config_dir_lib noop
+
+  def config_dir_ext(rel)
+    extconf if extdir?(curr_srcdir())
+  end
+
+  alias config_dir_data noop
+  alias config_dir_conf noop
+  alias config_dir_man noop
+
+  def extconf
+    ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt
+  end
+
+  #
+  # TASK setup
+  #
+
+  def exec_setup
+    exec_task_traverse 'setup'
+  end
+
+  def setup_dir_bin(rel)
+    files_of(curr_srcdir()).each do |fname|
+      update_shebang_line "#{curr_srcdir()}/#{fname}"
+    end
+  end
+
+  alias setup_dir_lib noop
+
+  def setup_dir_ext(rel)
+    make if extdir?(curr_srcdir())
+  end
+
+  alias setup_dir_data noop
+  alias setup_dir_conf noop
+  alias setup_dir_man noop
+
+  def update_shebang_line(path)
+    return if no_harm?
+    return if config('shebang') == 'never'
+    old = Shebang.load(path)
+    if old
+      $stderr.puts "warning: #{path}: Shebang line includes too many args.  It is not portable and your program may not work." if old.args.size > 1
+      new = new_shebang(old)
+      return if new.to_s == old.to_s
+    else
+      return unless config('shebang') == 'all'
+      new = Shebang.new(config('rubypath'))
+    end
+    $stderr.puts "updating shebang: #{File.basename(path)}" if verbose?
+    open_atomic_writer(path) {|output|
+      File.open(path, 'rb') {|f|
+        f.gets if old   # discard
+        output.puts new.to_s
+        output.print f.read
+      }
+    }
+  end
+
+  def new_shebang(old)
+    if /\Aruby/ =~ File.basename(old.cmd)
+      Shebang.new(config('rubypath'), old.args)
+    elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby'
+      Shebang.new(config('rubypath'), old.args[1..-1])
+    else
+      return old unless config('shebang') == 'all'
+      Shebang.new(config('rubypath'))
+    end
+  end
+
+  def open_atomic_writer(path, &block)
+    tmpfile = File.basename(path) + '.tmp'
+    begin
+      File.open(tmpfile, 'wb', &block)
+      File.rename tmpfile, File.basename(path)
+    ensure
+      File.unlink tmpfile if File.exist?(tmpfile)
+    end
+  end
+
+  class Shebang
+    def Shebang.load(path)
+      line = nil
+      File.open(path) {|f|
+        line = f.gets
+      }
+      return nil unless /\A#!/ =~ line
+      parse(line)
+    end
+
+    def Shebang.parse(line)
+      cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ')
+      new(cmd, args)
+    end
+
+    def initialize(cmd, args = [])
+      @cmd = cmd
+      @args = args
+    end
+
+    attr_reader :cmd
+    attr_reader :args
+
+    def to_s
+      "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}")
+    end
+  end
+
+  #
+  # TASK install
+  #
+
+  def exec_install
+    rm_f 'InstalledFiles'
+    exec_task_traverse 'install'
+  end
+
+  def install_dir_bin(rel)
+    install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755
+  end
+
+  def install_dir_lib(rel)
+    install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644
+  end
+
+  def install_dir_ext(rel)
+    return unless extdir?(curr_srcdir())
+    install_files rubyextentions('.'),
+                  "#{config('sodir')}/#{File.dirname(rel)}",
+                  0555
+  end
+
+  def install_dir_data(rel)
+    install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644
+  end
+
+  def install_dir_conf(rel)
+    # FIXME: should not remove current config files
+    # (rename previous file to .old/.org)
+    install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644
+  end
+
+  def install_dir_man(rel)
+    install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644
+  end
+
+  def install_files(list, dest, mode)
+    mkdir_p dest, @config.install_prefix
+    list.each do |fname|
+      install fname, dest, mode, @config.install_prefix
+    end
+  end
+
+  def libfiles
+    glob_reject(%w(*.y *.output), targetfiles())
+  end
+
+  def rubyextentions(dir)
+    ents = glob_select("*.#{@config.dllext}", targetfiles())
+    if ents.empty?
+      setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first"
+    end
+    ents
+  end
+
+  def targetfiles
+    mapdir(existfiles() - hookfiles())
+  end
+
+  def mapdir(ents)
+    ents.map {|ent|
+      if File.exist?(ent)
+      then ent                         # objdir
+      else "#{curr_srcdir()}/#{ent}"   # srcdir
+      end
+    }
+  end
+
+  # picked up many entries from cvs-1.11.1/src/ignore.c
+  JUNK_FILES = %w( 
+    core RCSLOG tags TAGS .make.state
+    .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb
+    *~ *.old *.bak *.BAK *.orig *.rej _$* *$
+
+    *.org *.in .*
+  )
+
+  def existfiles
+    glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.')))
+  end
+
+  def hookfiles
+    %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt|
+      %w( config setup install clean ).map {|t| sprintf(fmt, t) }
+    }.flatten
+  end
+
+  def glob_select(pat, ents)
+    re = globs2re([pat])
+    ents.select {|ent| re =~ ent }
+  end
+
+  def glob_reject(pats, ents)
+    re = globs2re(pats)
+    ents.reject {|ent| re =~ ent }
+  end
+
+  GLOB2REGEX = {
+    '.' => '\.',
+    '$' => '\$',
+    '#' => '\#',
+    '*' => '.*'
+  }
+
+  def globs2re(pats)
+    /\A(?:#{
+      pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|')
+    })\z/
+  end
+
+  #
+  # TASK test
+  #
+
+  TESTDIR = 'test'
+
+  def exec_test
+    unless File.directory?('test')
+      $stderr.puts 'no test in this package' if verbose?
+      return
+    end
+    $stderr.puts 'Running tests...' if verbose?
+    begin
+      require 'test/unit'
+    rescue LoadError
+      setup_rb_error 'test/unit cannot loaded.  You need Ruby 1.8 or later to invoke this task.'
+    end
+    runner = Test::Unit::AutoRunner.new(true)
+    runner.to_run << TESTDIR
+    runner.run
+  end
+
+  #
+  # TASK clean
+  #
+
+  def exec_clean
+    exec_task_traverse 'clean'
+    rm_f @config.savefile
+    rm_f 'InstalledFiles'
+  end
+
+  alias clean_dir_bin noop
+  alias clean_dir_lib noop
+  alias clean_dir_data noop
+  alias clean_dir_conf noop
+  alias clean_dir_man noop
+
+  def clean_dir_ext(rel)
+    return unless extdir?(curr_srcdir())
+    make 'clean' if File.file?('Makefile')
+  end
+
+  #
+  # TASK distclean
+  #
+
+  def exec_distclean
+    exec_task_traverse 'distclean'
+    rm_f @config.savefile
+    rm_f 'InstalledFiles'
+  end
+
+  alias distclean_dir_bin noop
+  alias distclean_dir_lib noop
+
+  def distclean_dir_ext(rel)
+    return unless extdir?(curr_srcdir())
+    make 'distclean' if File.file?('Makefile')
+  end
+
+  alias distclean_dir_data noop
+  alias distclean_dir_conf noop
+  alias distclean_dir_man noop
+
+  #
+  # Traversing
+  #
+
+  def exec_task_traverse(task)
+    run_hook "pre-#{task}"
+    FILETYPES.each do |type|
+      if type == 'ext' and config('without-ext') == 'yes'
+        $stderr.puts 'skipping ext/* by user option' if verbose?
+        next
+      end
+      traverse task, type, "#{task}_dir_#{type}"
+    end
+    run_hook "post-#{task}"
+  end
+
+  def traverse(task, rel, mid)
+    dive_into(rel) {
+      run_hook "pre-#{task}"
+      __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '')
+      directories_of(curr_srcdir()).each do |d|
+        traverse task, "#{rel}/#{d}", mid
+      end
+      run_hook "post-#{task}"
+    }
+  end
+
+  def dive_into(rel)
+    return unless File.dir?("#{@srcdir}/#{rel}")
+
+    dir = File.basename(rel)
+    Dir.mkdir dir unless File.dir?(dir)
+    prevdir = Dir.pwd
+    Dir.chdir dir
+    $stderr.puts '---> ' + rel if verbose?
+    @currdir = rel
+    yield
+    Dir.chdir prevdir
+    $stderr.puts '<--- ' + rel if verbose?
+    @currdir = File.dirname(rel)
+  end
+
+  def run_hook(id)
+    path = [ "#{curr_srcdir()}/#{id}",
+             "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) }
+    return unless path
+    begin
+      instance_eval File.read(path), path, 1
+    rescue
+      raise if $DEBUG
+      setup_rb_error "hook #{path} failed:\n" + $!.message
+    end
+  end
+
+end   # class Installer
+
+
+class SetupError < StandardError; end
+
+def setup_rb_error(msg)
+  raise SetupError, msg
+end
+
+if $0 == __FILE__
+  begin
+    ToplevelInstaller.invoke
+  rescue SetupError
+    raise if $DEBUG
+    $stderr.puts $!.message
+    $stderr.puts "Try 'ruby #{$0} --help' for detailed usage."
+    exit 1
+  end
+end
diff --git a/test/rb/Makefile b/test/rb/Makefile
new file mode 100644
index 0000000..8b3cb98
--- /dev/null
+++ b/test/rb/Makefile
@@ -0,0 +1,18 @@
+# Makefile for Thrift test project.
+# 
+# Author:
+#   Mark Slee <mcslee@facebook.com>
+
+# Default target is everything
+target: all
+
+# Tools
+THRIFT = ../../compiler/cpp/thrift
+
+all: stubs
+
+stubs: ../ThriftTest.thrift
+	$(THRIFT) --rb ../ThriftTest.thrift
+
+clean:
+	rm -fr gen-rb
diff --git a/test/rb/TestClient.rb b/test/rb/TestClient.rb
new file mode 100755
index 0000000..80d2f7b
--- /dev/null
+++ b/test/rb/TestClient.rb
@@ -0,0 +1,31 @@
+#!/usr/bin/ruby
+
+$:.push('gen-rb')
+$:.push('../../lib/ruby/lib')
+
+require 'thrift/transport/tsocket'
+require 'thrift/protocol/tbinaryprotocol'
+require 'ThriftTest'
+
+s = TSocket.new('localhost', 9090)
+p = TBinaryProtocol.new(s)
+c = ThriftTest::Client.new(p)
+
+s.open()
+
+puts c.testString('string')
+puts c.testByte(8)
+puts c.testByte(-8)
+puts c.testI32(32)
+puts c.testI32(-32)
+puts c.testI64(64)
+puts c.testI64(-64)
+puts c.testDouble(3.14)
+puts c.testDouble(-3.14)
+puts c.testMap({1 => 1, 2 => 2, 3 => 3})
+puts c.testList([1,2,3,4,5])
+puts c.testSet({1 => true, 2 => true, 3 => true})
+
+s.close()
+
+