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()
+
+