diff options
| author | INADA Naoki <songofacandy@gmail.com> | 2010-09-02 01:30:32 +0900 |
|---|---|---|
| committer | INADA Naoki <songofacandy@gmail.com> | 2010-09-02 01:30:32 +0900 |
| commit | 623df2357025a860578db54683aa10ddea7d261d (patch) | |
| tree | 697b5401dc7ca2108600464ae905aa688ae96fce /ruby | |
| parent | 4a15d8b6d2b69bdc1de0b0a7f643b02e00100e66 (diff) | |
| parent | 558e9c21edf3cee5813aaa0e7797509eec5d43fb (diff) | |
| download | msgpack-python-623df2357025a860578db54683aa10ddea7d261d.tar.gz | |
Merge branch 'master' of github.com:msgpack/msgpack
Diffstat (limited to 'ruby')
| -rw-r--r-- | ruby/ChangeLog | 6 | ||||
| -rw-r--r-- | ruby/README | 26 | ||||
| -rw-r--r-- | ruby/compat.h | 77 | ||||
| -rw-r--r-- | ruby/extconf.rb | 3 | ||||
| -rwxr-xr-x | ruby/makegem.sh | 7 | ||||
| -rw-r--r-- | ruby/msgpack.gemspec | 7 | ||||
| -rw-r--r-- | ruby/pack.c | 156 | ||||
| -rw-r--r-- | ruby/pack.h | 2 | ||||
| -rw-r--r-- | ruby/rbinit.c | 41 | ||||
| -rw-r--r-- | ruby/test/test_cases.rb | 46 | ||||
| -rw-r--r-- | ruby/test/test_encoding.rb | 68 | ||||
| -rw-r--r-- | ruby/test/test_helper.rb | 7 | ||||
| -rw-r--r-- | ruby/test/test_pack_unpack.rb (renamed from ruby/msgpack_test.rb) | 72 | ||||
| -rw-r--r-- | ruby/unpack.c | 435 | ||||
| -rw-r--r-- | ruby/unpack.h | 2 | ||||
| -rw-r--r-- | ruby/version.rb | 3 |
16 files changed, 851 insertions, 107 deletions
diff --git a/ruby/ChangeLog b/ruby/ChangeLog index e69de29..d3a7282 100644 --- a/ruby/ChangeLog +++ b/ruby/ChangeLog @@ -0,0 +1,6 @@ + +2010-06-29 version 0.4.3: + + * Adds MessagePack::VERSION constant + * Fixes SEGV problem caused by GC bug at MessagePack_Unpacker_mark + diff --git a/ruby/README b/ruby/README index 859ae2b..051a769 100644 --- a/ruby/README +++ b/ruby/README @@ -1,29 +1,37 @@ = MessagePack - == Description +MessagePack is a binary-based efficient object serialization library. +It enables to exchange structured objects between many languages like JSON. +But unlike JSON, it is very fast and small. -== Installation +Simple usage is as follows: -=== Archive Installation + require 'msgpack' + msg = [1,2,3].to_msgpack #=> "\x93\x01\x02\x03" + MessagePack.unpack(msg) #=> [1,2,3] - rake install +Use MessagePack::Unpacker for streaming deserialization. -=== Gem Installation - gem install msgpack +== Installation +=== Archive Installation -== Features/Problems + ruby extconf.rb + make + make install +=== Gem Installation -== Synopsis + gem install msgpack == Copyright Author:: frsyuki <frsyuki@users.sourceforge.jp> -Copyright:: Copyright (c) 2008-2009 frsyuki +Copyright:: Copyright (c) 2008-2010 FURUHASHI Sadayuki License:: Apache License, Version 2.0 + diff --git a/ruby/compat.h b/ruby/compat.h new file mode 100644 index 0000000..d7a2ca7 --- /dev/null +++ b/ruby/compat.h @@ -0,0 +1,77 @@ +/* + * MessagePack for Ruby + * + * Copyright (C) 2008-2010 FURUHASHI Sadayuki + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef COMPAT_H__ +#define COMPAT_H__ + + +#ifdef HAVE_RUBY_ENCODING_H +#include "ruby/encoding.h" +#define COMPAT_HAVE_ENCODING +extern int s_enc_utf8; +extern int s_enc_ascii8bit; +extern int s_enc_usascii; +extern VALUE s_enc_utf8_value; +#endif + +#ifdef RUBY_VM +#define COMPAT_RERAISE rb_exc_raise(rb_errinfo()) +#else +#define COMPAT_RERAISE rb_exc_raise(ruby_errinfo) +#endif + + +/* ruby 1.8 and Rubinius */ +#ifndef RBIGNUM_POSITIVE_P +# ifdef RUBINIUS +# define RBIGNUM_POSITIVE_P(b) (rb_funcall(b, rb_intern(">="), 1, INT2FIX(0)) == Qtrue) +# else +# define RBIGNUM_POSITIVE_P(b) (RBIGNUM(b)->sign) +# endif +#endif + + +/* Rubinius */ +#ifdef RUBINIUS +static inline void rb_gc_enable() { return; } +static inline void rb_gc_disable() { return; } +#endif + + +/* ruby 1.8.5 */ +#ifndef RSTRING_PTR +#define RSTRING_PTR(s) (RSTRING(s)->ptr) +#endif + +/* ruby 1.8.5 */ +#ifndef RSTRING_LEN +#define RSTRING_LEN(s) (RSTRING(s)->len) +#endif + +/* ruby 1.8.5 */ +#ifndef RARRAY_PTR +#define RARRAY_PTR(s) (RARRAY(s)->ptr) +#endif + +/* ruby 1.8.5 */ +#ifndef RARRAY_LEN +#define RARRAY_LEN(s) (RARRAY(s)->len) +#endif + + +#endif /* compat.h */ + diff --git a/ruby/extconf.rb b/ruby/extconf.rb index e6d4bd6..f1d44ec 100644 --- a/ruby/extconf.rb +++ b/ruby/extconf.rb @@ -1,4 +1,5 @@ require 'mkmf' -$CFLAGS << " -I.. -Wall -O4" +require './version.rb' +$CFLAGS << %[ -I.. -Wall -O4 -DMESSAGEPACK_VERSION=\\"#{MessagePack::VERSION}\\" -g] create_makefile('msgpack') diff --git a/ruby/makegem.sh b/ruby/makegem.sh index 5ea66f1..bf30cd4 100755 --- a/ruby/makegem.sh +++ b/ruby/makegem.sh @@ -8,15 +8,20 @@ cp pack.h ext/ cp rbinit.c ext/ cp unpack.c ext/ cp unpack.h ext/ +cp version.rb ext/ cp ../msgpack/pack_define.h msgpack/ cp ../msgpack/pack_template.h msgpack/ cp ../msgpack/unpack_define.h msgpack/ cp ../msgpack/unpack_template.h msgpack/ cp ../msgpack/sysdep.h msgpack/ -cat msgpack_test.rb | sed "s/require ['\"]msgpack['\"]/require File.dirname(__FILE__) + '\/test_helper.rb'/" > test/msgpack_test.rb +cp ../test/cases.mpac test/ +cp ../test/cases_compact.mpac test/ +cp ../test/cases.json test/ gem build msgpack.gemspec +rdoc rbinit.c pack.c unpack.c + if [ $? -eq 0 ]; then rm -rf ext msgpack test/msgpack_test.rb fi diff --git a/ruby/msgpack.gemspec b/ruby/msgpack.gemspec index c5e8c8c..95a2bd0 100644 --- a/ruby/msgpack.gemspec +++ b/ruby/msgpack.gemspec @@ -1,14 +1,15 @@ +require './version.rb' Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.name = "msgpack" - s.version = "0.3.9" + s.version = MessagePack::VERSION s.summary = "MessagePack, a binary-based efficient data interchange format." s.author = "FURUHASHI Sadayuki" s.email = "frsyuki@users.sourceforge.jp" s.homepage = "http://msgpack.sourceforge.net/" s.rubyforge_project = "msgpack" - s.has_rdoc = false - s.extra_rdoc_files = ["README", "ChangeLog", "AUTHORS"] + s.has_rdoc = true + s.rdoc_options = ["ext"] s.require_paths = ["lib"] s.files = Dir["ext/**/*", "msgpack/**/*", "test/**/*"] s.test_files = Dir["test/test_*.rb"] diff --git a/ruby/pack.c b/ruby/pack.c index 387bab6..8ce46aa 100644 --- a/ruby/pack.c +++ b/ruby/pack.c @@ -16,6 +16,8 @@ * limitations under the License. */ #include "ruby.h" +#include "compat.h" + #include "msgpack/pack_define.h" static ID s_to_msgpack; @@ -51,6 +53,15 @@ static ID s_append; rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc); \ } + +/* + * Document-method: NilClass#to_msgpack + * + * call-seq: + * nil.to_msgpack(out = '') -> String + * + * Serializes the nil into raw bytes. + */ static VALUE MessagePack_NilClass_to_msgpack(int argc, VALUE *argv, VALUE self) { ARG_BUFFER(out, argc, argv); @@ -58,6 +69,15 @@ static VALUE MessagePack_NilClass_to_msgpack(int argc, VALUE *argv, VALUE self) return out; } + +/* + * Document-method: TrueClass#to_msgpack + * + * call-seq: + * true.to_msgpack(out = '') -> String + * + * Serializes the true into raw bytes. + */ static VALUE MessagePack_TrueClass_to_msgpack(int argc, VALUE *argv, VALUE self) { ARG_BUFFER(out, argc, argv); @@ -65,6 +85,15 @@ static VALUE MessagePack_TrueClass_to_msgpack(int argc, VALUE *argv, VALUE self) return out; } + +/* + * Document-method: FalseClass#to_msgpack + * + * call-seq: + * false.to_msgpack(out = '') -> String + * + * Serializes false into raw bytes. + */ static VALUE MessagePack_FalseClass_to_msgpack(int argc, VALUE *argv, VALUE self) { ARG_BUFFER(out, argc, argv); @@ -73,6 +102,14 @@ static VALUE MessagePack_FalseClass_to_msgpack(int argc, VALUE *argv, VALUE self } +/* + * Document-method: Fixnum#to_msgpack + * + * call-seq: + * fixnum.to_msgpack(out = '') -> String + * + * Serializes the Fixnum into raw bytes. + */ static VALUE MessagePack_Fixnum_to_msgpack(int argc, VALUE *argv, VALUE self) { ARG_BUFFER(out, argc, argv); @@ -81,22 +118,34 @@ static VALUE MessagePack_Fixnum_to_msgpack(int argc, VALUE *argv, VALUE self) } -#ifndef RBIGNUM_SIGN // Ruby 1.8 -#define RBIGNUM_SIGN(b) (RBIGNUM(b)->sign) -#endif - +/* + * Document-method: Bignum#to_msgpack + * + * call-seq: + * bignum.to_msgpack(out = '') -> String + * + * Serializes the Bignum into raw bytes. + */ static VALUE MessagePack_Bignum_to_msgpack(int argc, VALUE *argv, VALUE self) { ARG_BUFFER(out, argc, argv); - // FIXME bignum - if(RBIGNUM_SIGN(self)) { // positive + if(RBIGNUM_POSITIVE_P(self)) { msgpack_pack_uint64(out, rb_big2ull(self)); - } else { // negative + } else { msgpack_pack_int64(out, rb_big2ll(self)); } return out; } + +/* + * Document-method: Float#to_msgpack + * + * call-seq: + * float.to_msgpack(out = '') -> String + * + * Serializes the Float into raw bytes. + */ static VALUE MessagePack_Float_to_msgpack(int argc, VALUE *argv, VALUE self) { ARG_BUFFER(out, argc, argv); @@ -104,28 +153,69 @@ static VALUE MessagePack_Float_to_msgpack(int argc, VALUE *argv, VALUE self) return out; } + +/* + * Document-method: String#to_msgpack + * + * call-seq: + * string.to_msgpack(out = '') -> String + * + * Serializes the String into raw bytes. + */ static VALUE MessagePack_String_to_msgpack(int argc, VALUE *argv, VALUE self) { ARG_BUFFER(out, argc, argv); +#ifdef COMPAT_HAVE_ENCODING + int enc = ENCODING_GET(self); + if(enc != s_enc_utf8 && enc != s_enc_ascii8bit && enc != s_enc_usascii) { + if(!ENC_CODERANGE_ASCIIONLY(self)) { + self = rb_str_encode(self, s_enc_utf8_value, 0, Qnil); + } + } +#endif msgpack_pack_raw(out, RSTRING_LEN(self)); msgpack_pack_raw_body(out, RSTRING_PTR(self), RSTRING_LEN(self)); return out; } + +/* + * Document-method: Symbol#to_msgpack + * + * call-seq: + * symbol.to_msgpack(out = '') -> String + * + * Serializes the Symbol into raw bytes. + */ static VALUE MessagePack_Symbol_to_msgpack(int argc, VALUE *argv, VALUE self) { +#ifdef COMPAT_HAVE_ENCODING + return MessagePack_String_to_msgpack(argc, argv, rb_id2str(SYM2ID(self))); +#else ARG_BUFFER(out, argc, argv); const char* name = rb_id2name(SYM2ID(self)); size_t len = strlen(name); msgpack_pack_raw(out, len); msgpack_pack_raw_body(out, name, len); return out; +#endif } + +/* + * Document-method: Array#to_msgpack + * + * call-seq: + * array.to_msgpack(out = '') -> String + * + * Serializes the Array into raw bytes. + * This calls to_msgpack method reflectively for internal elements. + */ static VALUE MessagePack_Array_to_msgpack(int argc, VALUE *argv, VALUE self) { ARG_BUFFER(out, argc, argv); - msgpack_pack_array(out, RARRAY_LEN(self)); + // FIXME check sizeof(long) > sizeof(unsigned int) && RARRAY_LEN(self) > UINT_MAX + msgpack_pack_array(out, (unsigned int)RARRAY_LEN(self)); VALUE* p = RARRAY_PTR(self); VALUE* const pend = p + RARRAY_LEN(self); for(;p != pend; ++p) { @@ -134,6 +224,7 @@ static VALUE MessagePack_Array_to_msgpack(int argc, VALUE *argv, VALUE self) return out; } + #ifndef RHASH_SIZE // Ruby 1.8 #define RHASH_SIZE(h) (RHASH(h)->tbl ? RHASH(h)->tbl->num_entries : 0) #endif @@ -146,15 +237,36 @@ static int MessagePack_Hash_to_msgpack_foreach(VALUE key, VALUE value, VALUE out return ST_CONTINUE; } +/* + * Document-method: Hash#to_msgpack + * + * call-seq: + * hash.to_msgpack(out = '') -> String + * + * Serializes the Hash into raw bytes. + * This calls to_msgpack method reflectively for internal keys and values. + */ static VALUE MessagePack_Hash_to_msgpack(int argc, VALUE *argv, VALUE self) { ARG_BUFFER(out, argc, argv); - msgpack_pack_map(out, RHASH_SIZE(self)); + // FIXME check sizeof(st_index_t) > sizeof(unsigned int) && RARRAY_LEN(self) > UINT_MAX + msgpack_pack_map(out, (unsigned int)RHASH_SIZE(self)); rb_hash_foreach(self, MessagePack_Hash_to_msgpack_foreach, out); return out; } +/** + * Document-method: MessagePack.pack + * + * call-seq: + * MessagePack.pack(object, out = '') -> String + * + * Serializes the object into raw bytes. The encoding of the string is ASCII-8BIT on Ruby 1.9. + * This method is same as object.to_msgpack(out = ''). + * + * _out_ is an object that implements *<<* method like String or IO. + */ static VALUE MessagePack_pack(int argc, VALUE* argv, VALUE self) { VALUE out; @@ -173,16 +285,22 @@ void Init_msgpack_pack(VALUE mMessagePack) { s_to_msgpack = rb_intern("to_msgpack"); s_append = rb_intern("<<"); - rb_define_method_id(rb_cNilClass, s_to_msgpack, MessagePack_NilClass_to_msgpack, -1); - rb_define_method_id(rb_cTrueClass, s_to_msgpack, MessagePack_TrueClass_to_msgpack, -1); - rb_define_method_id(rb_cFalseClass, s_to_msgpack, MessagePack_FalseClass_to_msgpack, -1); - rb_define_method_id(rb_cFixnum, s_to_msgpack, MessagePack_Fixnum_to_msgpack, -1); - rb_define_method_id(rb_cBignum, s_to_msgpack, MessagePack_Bignum_to_msgpack, -1); - rb_define_method_id(rb_cFloat, s_to_msgpack, MessagePack_Float_to_msgpack, -1); - rb_define_method_id(rb_cString, s_to_msgpack, MessagePack_String_to_msgpack, -1); - rb_define_method_id(rb_cArray, s_to_msgpack, MessagePack_Array_to_msgpack, -1); - rb_define_method_id(rb_cHash, s_to_msgpack, MessagePack_Hash_to_msgpack, -1); - rb_define_method_id(rb_cSymbol, s_to_msgpack, MessagePack_Symbol_to_msgpack, -1); + + rb_define_method(rb_cNilClass, "to_msgpack", MessagePack_NilClass_to_msgpack, -1); + rb_define_method(rb_cTrueClass, "to_msgpack", MessagePack_TrueClass_to_msgpack, -1); + rb_define_method(rb_cFalseClass, "to_msgpack", MessagePack_FalseClass_to_msgpack, -1); + rb_define_method(rb_cFixnum, "to_msgpack", MessagePack_Fixnum_to_msgpack, -1); + rb_define_method(rb_cBignum, "to_msgpack", MessagePack_Bignum_to_msgpack, -1); + rb_define_method(rb_cFloat, "to_msgpack", MessagePack_Float_to_msgpack, -1); + rb_define_method(rb_cString, "to_msgpack", MessagePack_String_to_msgpack, -1); + rb_define_method(rb_cArray, "to_msgpack", MessagePack_Array_to_msgpack, -1); + rb_define_method(rb_cHash, "to_msgpack", MessagePack_Hash_to_msgpack, -1); + rb_define_method(rb_cSymbol, "to_msgpack", MessagePack_Symbol_to_msgpack, -1); + + /** + * MessagePack module is defined in rbinit.c file. + * mMessagePack = rb_define_module("MessagePack"); + */ rb_define_module_function(mMessagePack, "pack", MessagePack_pack, -1); } diff --git a/ruby/pack.h b/ruby/pack.h index c9b08a4..f162a86 100644 --- a/ruby/pack.h +++ b/ruby/pack.h @@ -1,7 +1,7 @@ /* * MessagePack for Ruby packing routine * - * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * Copyright (C) 2008-2010 FURUHASHI Sadayuki * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ruby/rbinit.c b/ruby/rbinit.c index 80d1d8c..1d1cbc6 100644 --- a/ruby/rbinit.c +++ b/ruby/rbinit.c @@ -1,7 +1,7 @@ /* * MessagePack for Ruby * - * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * Copyright (C) 2008-2010 FURUHASHI Sadayuki * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,13 +17,50 @@ */ #include "pack.h" #include "unpack.h" +#include "compat.h" static VALUE mMessagePack; +#ifdef COMPAT_HAVE_ENCODING +int s_enc_utf8; +int s_enc_ascii8bit; +int s_enc_usascii; +VALUE s_enc_utf8_value; +#endif + +/** + * Document-module: MessagePack + * + * MessagePack is a binary-based efficient object serialization library. + * It enables to exchange structured objects between many languages like JSON. + * But unlike JSON, it is very fast and small. + * + * You can install MessagePack with rubygems. + * + * gem install msgpack + * + * Simple usage is as follows: + * + * require 'msgpack' + * msg = [1,2,3].to_msgpack #=> "\x93\x01\x02\x03" + * MessagePack.unpack(msg) #=> [1,2,3] + * + * Use Unpacker class for streaming deserialization. + * + */ void Init_msgpack(void) { mMessagePack = rb_define_module("MessagePack"); + + rb_define_const(mMessagePack, "VERSION", rb_str_new2(MESSAGEPACK_VERSION)); + +#ifdef COMPAT_HAVE_ENCODING + s_enc_ascii8bit = rb_ascii8bit_encindex(); + s_enc_utf8 = rb_utf8_encindex(); + s_enc_usascii = rb_usascii_encindex(); + s_enc_utf8_value = rb_enc_from_encoding(rb_utf8_encoding()); +#endif + Init_msgpack_unpack(mMessagePack); Init_msgpack_pack(mMessagePack); } - diff --git a/ruby/test/test_cases.rb b/ruby/test/test_cases.rb new file mode 100644 index 0000000..bfb752e --- /dev/null +++ b/ruby/test/test_cases.rb @@ -0,0 +1,46 @@ +#!/usr/bin/env ruby +here = File.dirname(__FILE__) +require "#{here}/test_helper" + +begin +require 'json' +rescue LoadError +require 'rubygems' +require 'json' +end + +CASES_PATH = "#{here}/cases.mpac" +CASES_COMPACT_PATH = "#{here}/cases_compact.mpac" +CASES_JSON_PATH = "#{here}/cases.json" + +class MessagePackTestCases < Test::Unit::TestCase + def feed_file(path) + pac = MessagePack::Unpacker.new + pac.feed File.read(path) + pac + end + + def test_compare_compact + pac = feed_file(CASES_PATH) + cpac = feed_file(CASES_COMPACT_PATH) + + objs = []; pac.each {| obj| objs << obj } + cobjs = []; cpac.each {|cobj| cobjs << cobj } + + objs.zip(cobjs).each {|obj, cobj| + assert_equal(obj, cobj) + } + end + + def test_compare_json + pac = feed_file(CASES_PATH) + + objs = []; pac.each {|obj| objs << obj } + jobjs = JSON.load File.read(CASES_JSON_PATH) + + objs.zip(jobjs) {|obj, jobj| + assert_equal(obj, jobj) + } + end +end + diff --git a/ruby/test/test_encoding.rb b/ruby/test/test_encoding.rb new file mode 100644 index 0000000..2cf0767 --- /dev/null +++ b/ruby/test/test_encoding.rb @@ -0,0 +1,68 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__)+'/test_helper' + +if RUBY_VERSION < "1.9" + exit +end + +class MessagePackTestEncoding < Test::Unit::TestCase + def self.it(name, &block) + define_method("test_#{name}", &block) + end + + it "US-ASCII" do + check_unpack "abc".force_encoding("US-ASCII") + end + + it "UTF-8 ascii" do + check_unpack "abc".force_encoding("UTF-8") + end + + it "UTF-8 mbstr" do + check_unpack "\xE3\x81\x82".force_encoding("UTF-8") + end + + it "UTF-8 invalid" do + check_unpack "\xD0".force_encoding("UTF-8") + end + + it "ASCII-8BIT" do + check_unpack "\xD0".force_encoding("ASCII-8BIT") + end + + it "EUC-JP" do + x = "\xA4\xA2".force_encoding("EUC-JP") + check_unpack(x) + end + + it "EUC-JP invalid" do + begin + "\xD0".force_encoding("EUC-JP").to_msgpack + assert(false) + rescue Encoding::InvalidByteSequenceError + assert(true) + end + end + + private + def check_unpack(str) + if str.encoding.to_s == "ASCII-8BIT" + should_str = str.dup.force_encoding("UTF-8") + else + should_str = str.encode("UTF-8") + end + + raw = str.to_msgpack + r = MessagePack.unpack(str.to_msgpack) + assert_equal(r.encoding.to_s, "UTF-8") + assert_equal(r, should_str.force_encoding("UTF-8")) + + if str.valid_encoding? + sym = str.to_sym + r = MessagePack.unpack(sym.to_msgpack) + assert_equal(r.encoding.to_s, "UTF-8") + assert_equal(r, should_str.force_encoding("UTF-8")) + end + end +end + diff --git a/ruby/test/test_helper.rb b/ruby/test/test_helper.rb index 6a63489..4def861 100644 --- a/ruby/test/test_helper.rb +++ b/ruby/test/test_helper.rb @@ -1,3 +1,10 @@ require 'test/unit' +begin +require File.dirname(__FILE__) + '/../msgpack' +rescue LoadError require File.dirname(__FILE__) + '/../lib/msgpack' +end +if ENV["GC_STRESS"] + GC.stress = true +end diff --git a/ruby/msgpack_test.rb b/ruby/test/test_pack_unpack.rb index 8cbb586..545e593 100644 --- a/ruby/msgpack_test.rb +++ b/ruby/test/test_pack_unpack.rb @@ -1,8 +1,7 @@ #!/usr/bin/env ruby -require 'msgpack' -require 'test/unit' +require File.dirname(__FILE__)+'/test_helper' -class MessagePackTestFormat < Test::Unit::TestCase +class MessagePackTestPackUnpack < Test::Unit::TestCase def self.it(name, &block) define_method("test_#{name}", &block) end @@ -154,7 +153,8 @@ class MessagePackTestFormat < Test::Unit::TestCase end it "{1=>1}" do - match ({1=>1}), "\x81\x01\x01" + obj = {1=>1} + match obj, "\x81\x01\x01" end it "1.0" do @@ -166,27 +166,32 @@ class MessagePackTestFormat < Test::Unit::TestCase end it "[0, 1, ..., 14]" do - match (0..14).to_a, "\x9f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" + obj = (0..14).to_a + match obj, "\x9f\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" end it "[0, 1, ..., 15]" do - match (0..15).to_a, "\xdc\x00\x10\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + obj = (0..15).to_a + match obj, "\xdc\x00\x10\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" end it "{}" do - match ({}), "\x80" + obj = {} + match obj, "\x80" end - it "{0=>0, 1=>1, ..., 14=>14}" do - a = (0..14).to_a; - match Hash[*a.zip(a).flatten], "\x8f\x05\x05\x0b\x0b\x00\x00\x06\x06\x0c\x0c\x01\x01\x07\x07\x0d\x0d\x02\x02\x08\x08\x0e\x0e\x03\x03\x09\x09\x04\x04\x0a\x0a" - end - - it "{0=>0, 1=>1, ..., 15=>15}" do - a = (0..15).to_a; - match Hash[*a.zip(a).flatten], "\xde\x00\x10\x05\x05\x0b\x0b\x00\x00\x06\x06\x0c\x0c\x01\x01\x07\x07\x0d\x0d\x02\x02\x08\x08\x0e\x0e\x03\x03\x09\x09\x0f\x0f\x04\x04\x0a\x0a" - end +## FIXME +# it "{0=>0, 1=>1, ..., 14=>14}" do +# a = (0..14).to_a; +# match Hash[*a.zip(a).flatten], "\x8f\x05\x05\x0b\x0b\x00\x00\x06\x06\x0c\x0c\x01\x01\x07\x07\x0d\x0d\x02\x02\x08\x08\x0e\x0e\x03\x03\x09\x09\x04\x04\x0a\x0a" +# end +# +# it "{0=>0, 1=>1, ..., 15=>15}" do +# a = (0..15).to_a; +# match Hash[*a.zip(a).flatten], "\xde\x00\x10\x05\x05\x0b\x0b\x00\x00\x06\x06\x0c\x0c\x01\x01\x07\x07\x0d\x0d\x02\x02\x08\x08\x0e\x0e\x03\x03\x09\x09\x0f\x0f\x04\x04\x0a\x0a" +# end +## FIXME # it "fixmap" do # check_map 1, 0 # check_map 1, (1<<4)-1 @@ -202,6 +207,37 @@ class MessagePackTestFormat < Test::Unit::TestCase # #check_map 5, (1<<32)-1 # memory error # end + it "buffer" do + str = "a"*32*1024*4 + raw = str.to_msgpack + pac = MessagePack::Unpacker.new + + len = 0 + parsed = false + + n = 655 + time = raw.size / n + time += 1 unless raw.size % n == 0 + off = 0 + + time.times do + assert(!parsed) + + fe = raw[off, n] + assert(fe.length > 0) + off += fe.length + + pac.feed fe + pac.each {|obj| + assert(!parsed) + assert_equal(obj, str) + parsed = true + } + end + + assert(parsed) + end + it "gc mark" do obj = [{["a","b"]=>["c","d"]}, ["e","f"], "d"] num = 4 @@ -244,6 +280,10 @@ class MessagePackTestFormat < Test::Unit::TestCase assert_equal(parsed, num) end + it "MessagePack::VERSION constant" do + p MessagePack::VERSION + end + private def check(len, obj) v = obj.to_msgpack diff --git a/ruby/unpack.c b/ruby/unpack.c index 3a95e5a..2d10e75 100644 --- a/ruby/unpack.c +++ b/ruby/unpack.c @@ -16,22 +16,24 @@ * limitations under the License. */ #include "ruby.h" +#include "compat.h" #include "msgpack/unpack_define.h" static ID s_sysread; static ID s_readpartial; -#ifdef HAVE_RUBY_ENCODING_H -#include "ruby/encoding.h" -int s_ascii_8bit; -#endif +struct unpack_buffer { + size_t size; + size_t free; + char* ptr; +}; typedef struct { int finished; VALUE source; size_t offset; - VALUE buffer; + struct unpack_buffer buffer; VALUE stream; VALUE streambuf; ID stream_append_method; @@ -124,7 +126,17 @@ static inline int template_callback_map_item(unpack_user* u, VALUE* c, VALUE k, #endif static inline int template_callback_raw(unpack_user* u, const char* b, const char* p, unsigned int l, VALUE* o) -{ *o = (l <= COW_MIN_SIZE) ? rb_str_new(p, l) : rb_str_substr(u->source, p - b, l); return 0; } +{ + if(u->source == Qnil || l <= COW_MIN_SIZE) { + *o = rb_str_new(p, l); + } else { + *o = rb_str_substr(u->source, p - b, l); + } +#ifdef COMPAT_HAVE_ENCODING + ENCODING_SET(*o, s_enc_utf8); +#endif + return 0; +} #include "msgpack/unpack_template.h" @@ -147,11 +159,7 @@ static inline int template_callback_raw(unpack_user* u, const char* b, const cha static VALUE template_execute_rescue(VALUE nouse) { rb_gc_enable(); -#ifdef RUBY_VM - rb_exc_raise(rb_errinfo()); -#else - rb_exc_raise(ruby_errinfo); -#endif + COMPAT_RERAISE; } static VALUE template_execute_do(VALUE argv) @@ -178,12 +186,6 @@ static int template_execute_wrap(msgpack_unpack_t* mp, (VALUE)from, }; -#ifdef HAVE_RUBY_ENCODING_H - // FIXME encodingをASCII-8BITにする - int enc_orig = rb_enc_get_index(str); - rb_enc_set_index(str, s_ascii_8bit); -#endif - // FIXME execute実行中はmp->topが更新されないのでGC markが機能しない rb_gc_disable(); @@ -192,33 +194,68 @@ static int template_execute_wrap(msgpack_unpack_t* mp, int ret = (int)rb_rescue(template_execute_do, (VALUE)args, template_execute_rescue, Qnil); + rb_gc_enable(); + + return ret; +} + +static int template_execute_wrap_each(msgpack_unpack_t* mp, + const char* ptr, size_t dlen, size_t* from) +{ + VALUE args[4] = { + (VALUE)mp, + (VALUE)ptr, + (VALUE)dlen, + (VALUE)from, + }; + + // FIXME execute実行中はmp->topが更新されないのでGC markが機能しない + rb_gc_disable(); + mp->user.source = Qnil; - rb_gc_enable(); + int ret = (int)rb_rescue(template_execute_do, (VALUE)args, + template_execute_rescue, Qnil); -#ifdef HAVE_RUBY_ENCODING_H - rb_enc_set_index(str, enc_orig); -#endif + rb_gc_enable(); return ret; } static VALUE cUnpacker; + + +/** + * Document-module: MessagePack::UnpackerError + * + */ static VALUE eUnpackError; +#ifndef MSGPACK_UNPACKER_BUFFER_INIT_SIZE +#define MSGPACK_UNPACKER_BUFFER_INIT_SIZE (32*1024) +#endif + +#ifndef MSGPACK_UNPACKER_BUFFER_RESERVE_SIZE +#define MSGPACK_UNPACKER_BUFFER_RESERVE_SIZE (8*1024) +#endif + static void MessagePack_Unpacker_free(void* data) { - if(data) { free(data); } + if(data) { + msgpack_unpack_t* mp = (msgpack_unpack_t*)data; + free(mp->user.buffer.ptr); + free(mp); + } } static void MessagePack_Unpacker_mark(msgpack_unpack_t *mp) { unsigned int i; - rb_gc_mark(mp->user.buffer); rb_gc_mark(mp->user.stream); rb_gc_mark(mp->user.streambuf); + rb_gc_mark_maybe(template_data(mp)); for(i=0; i < mp->top; ++i) { rb_gc_mark(mp->stack[i].obj); rb_gc_mark_maybe(mp->stack[i].map_key); @@ -229,6 +266,17 @@ static VALUE MessagePack_Unpacker_alloc(VALUE klass) { VALUE obj; msgpack_unpack_t* mp = ALLOC_N(msgpack_unpack_t, 1); + + // rb_gc_mark (not _maybe) is used for following member objects. + mp->user.stream = Qnil; + mp->user.streambuf = Qnil; + + mp->user.finished = 0; + mp->user.offset = 0; + mp->user.buffer.size = 0; + mp->user.buffer.free = 0; + mp->user.buffer.ptr = NULL; + obj = Data_Wrap_Struct(klass, MessagePack_Unpacker_mark, MessagePack_Unpacker_free, mp); return obj; @@ -243,6 +291,22 @@ static ID append_method_of(VALUE stream) } } +/** + * Document-method: MessagePack::Unpacker#initialize + * + * call-seq: + * MessagePack::Unpacker.new(stream = nil) + * + * Creates instance of MessagePack::Unpacker. + * + * You can specify a _stream_ for input stream. + * It is required to implement *sysread* or *readpartial* method. + * + * With the input stream, buffers will be feeded into the deserializer automatically. + * + * Without the input stream, use *feed* method manually. Or you can manage the buffer manually + * with *execute*, *finished?*, *data* and *reset* methods. + */ static VALUE MessagePack_Unpacker_initialize(int argc, VALUE *argv, VALUE self) { VALUE stream; @@ -259,21 +323,36 @@ static VALUE MessagePack_Unpacker_initialize(int argc, VALUE *argv, VALUE self) UNPACKER(self, mp); template_init(mp); - mp->user.finished = 0; - mp->user.offset = 0; - mp->user.buffer = rb_str_new("",0); mp->user.stream = stream; - mp->user.streambuf = rb_str_new("",0); + mp->user.streambuf = rb_str_buf_new(MSGPACK_UNPACKER_BUFFER_RESERVE_SIZE); mp->user.stream_append_method = append_method_of(stream); + return self; } + +/** + * Document-method: MessagePack::Unpacker#stream + * + * call-seq: + * unpacker.stream + * + * Gets the input stream. + */ static VALUE MessagePack_Unpacker_stream_get(VALUE self) { UNPACKER(self, mp); return mp->user.stream; } +/** + * Document-method: MessagePack::Unpacker#stream= + * + * call-seq: + * unpacker.stream = stream + * + * Resets the input stream. You can set nil not to use input stream. + */ static VALUE MessagePack_Unpacker_stream_set(VALUE self, VALUE val) { UNPACKER(self, mp); @@ -282,14 +361,103 @@ static VALUE MessagePack_Unpacker_stream_set(VALUE self, VALUE val) return val; } + +static void reserve_buffer(msgpack_unpack_t* mp, size_t require) +{ + struct unpack_buffer* buffer = &mp->user.buffer; + + if(buffer->size == 0) { + size_t nsize = MSGPACK_UNPACKER_BUFFER_INIT_SIZE; + while(nsize < require) { + nsize *= 2; + } + char* tmp = ALLOC_N(char, nsize); + buffer->ptr = tmp; + buffer->free = nsize; + buffer->size = 0; + return; + } + + if(buffer->size <= mp->user.offset) { + /* clear buffer and rewind offset */ + buffer->free += buffer->size; + buffer->size = 0; + mp->user.offset = 0; + } + + if(require <= buffer->free) { + /* enough free space */ + return; + } + + size_t nsize = (buffer->size + buffer->free) * 2; + + if(mp->user.offset <= buffer->size / 2) { + /* parsed less than half: realloc only */ + while(nsize < buffer->size + require) { + nsize *= 2; + } + char* tmp = REALLOC_N(buffer->ptr, char, nsize); + buffer->free = nsize - buffer->size; + buffer->ptr = tmp; + + } else { + /* parsed more than half: realloc and move */ + size_t not_parsed = buffer->size - mp->user.offset; + while(nsize < not_parsed + require) { + nsize *= 2; + } + char* tmp = REALLOC_N(buffer->ptr, char, nsize); + memcpy(tmp, tmp + mp->user.offset, not_parsed); + buffer->free = nsize - buffer->size; + buffer->size = not_parsed; + buffer->ptr = tmp; + mp->user.offset = 0; + } +} + +static inline void feed_buffer(msgpack_unpack_t* mp, const char* ptr, size_t len) +{ + struct unpack_buffer* buffer = &mp->user.buffer; + + if(buffer->free < len) { + reserve_buffer(mp, len); + } + memcpy(buffer->ptr + buffer->size, ptr, len); + buffer->size += len; + buffer->free -= len; +} + +/** + * Document-method: MessagePack::Unpacker#feed + * + * call-seq: + * unpacker.feed(data) + * + * Fills the internal buffer with the specified buffer. + */ static VALUE MessagePack_Unpacker_feed(VALUE self, VALUE data) { UNPACKER(self, mp); StringValue(data); - rb_str_cat(mp->user.buffer, RSTRING_PTR(data), RSTRING_LEN(data)); + feed_buffer(mp, RSTRING_PTR(data), RSTRING_LEN(data)); return Qnil; } +/** + * Document-method: MessagePack::Unpacker#fill + * + * call-seq: + * unpacker.fill -> length of read data + * + * Fills the internal buffer using the input stream. + * + * If the input stream is not specified, it returns nil. + * You can set it on *initialize* or *stream=* methods. + * + * This methods raises exceptions that _stream.sysread_ or + * _stream.readpartial_ method raises. + */ static VALUE MessagePack_Unpacker_fill(VALUE self) { UNPACKER(self, mp); @@ -298,21 +466,28 @@ static VALUE MessagePack_Unpacker_fill(VALUE self) return Qnil; } - long len; - if(RSTRING_LEN(mp->user.buffer) == 0) { - rb_funcall(mp->user.stream, mp->user.stream_append_method, 2, - LONG2FIX(64*1024), mp->user.buffer); - len = RSTRING_LEN(mp->user.buffer); - } else { - rb_funcall(mp->user.stream, mp->user.stream_append_method, 2, - LONG2FIX(64*1024), mp->user.streambuf); - len = RSTRING_LEN(mp->user.streambuf); - rb_str_cat(mp->user.buffer, RSTRING_PTR(mp->user.streambuf), RSTRING_LEN(mp->user.streambuf)); - } + rb_funcall(mp->user.stream, mp->user.stream_append_method, 2, + LONG2FIX(MSGPACK_UNPACKER_BUFFER_RESERVE_SIZE), + mp->user.streambuf); + + size_t len = RSTRING_LEN(mp->user.streambuf); + feed_buffer(mp, RSTRING_PTR(mp->user.streambuf), len); return LONG2FIX(len); } + +/** + * Document-method: MessagePack::Unpacker#each + * + * call-seq: + * unpacker.each {|object| } + * + * Deserializes objects repeatedly. This calls *fill* method automatically. + * + * UnpackError is throw when parse error is occured. + * This method raises exceptions that *fill* method raises. + */ static VALUE MessagePack_Unpacker_each(VALUE self) { UNPACKER(self, mp); @@ -323,7 +498,7 @@ static VALUE MessagePack_Unpacker_each(VALUE self) #endif while(1) { - if(RSTRING_LEN(mp->user.buffer) <= mp->user.offset) { + if(mp->user.buffer.size <= mp->user.offset) { do_fill: { VALUE len = MessagePack_Unpacker_fill(self); @@ -333,8 +508,9 @@ static VALUE MessagePack_Unpacker_each(VALUE self) } } - ret = template_execute_wrap(mp, mp->user.buffer, - RSTRING_LEN(mp->user.buffer), &mp->user.offset); + ret = template_execute_wrap_each(mp, + mp->user.buffer.ptr, mp->user.buffer.size, + &mp->user.offset); if(ret < 0) { rb_raise(eUnpackError, "parse error."); @@ -352,6 +528,7 @@ static VALUE MessagePack_Unpacker_each(VALUE self) return Qnil; } + static inline VALUE MessagePack_unpack_impl(VALUE self, VALUE data, unsigned long dlen) { msgpack_unpack_t mp; @@ -376,12 +553,34 @@ static inline VALUE MessagePack_unpack_impl(VALUE self, VALUE data, unsigned lon } } +/** + * Document-method: MessagePack::Unpacker.unpack_limit + * + * call-seq: + * MessagePack::Unpacker.unpack_limit(data, limit) -> object + * + * Deserializes one object over the specified buffer upto _limit_ bytes. + * + * UnpackError is throw when parse error is occured, the buffer is insufficient + * to deserialize one object or there are extra bytes. + */ static VALUE MessagePack_unpack_limit(VALUE self, VALUE data, VALUE limit) { CHECK_STRING_TYPE(data); return MessagePack_unpack_impl(self, data, NUM2ULONG(limit)); } +/** + * Document-method: MessagePack::Unpacker.unpack + * + * call-seq: + * MessagePack::Unpacker.unpack(data) -> object + * + * Deserializes one object over the specified buffer. + * + * UnpackError is throw when parse error is occured, the buffer is insufficient + * to deserialize one object or there are extra bytes. + */ static VALUE MessagePack_unpack(VALUE self, VALUE data) { CHECK_STRING_TYPE(data); @@ -411,7 +610,20 @@ static VALUE MessagePack_Unpacker_execute_impl(VALUE self, VALUE data, } } -/* compat */ +/** + * Document-method: MessagePack::Unpacker#execute_limit + * + * call-seq: + * unpacker.execute_limit(data, offset, limit) -> next offset + * + * Deserializes one object over the specified buffer from _offset_ bytes upto _limit_ bytes. + * + * This method doesn't use the internal buffer. + * + * Call *reset()* method before calling this method again. + * + * UnpackError is throw when parse error is occured. + */ static VALUE MessagePack_Unpacker_execute_limit(VALUE self, VALUE data, VALUE off, VALUE limit) { @@ -420,7 +632,24 @@ static VALUE MessagePack_Unpacker_execute_limit(VALUE self, VALUE data, (size_t)NUM2ULONG(off), (size_t)NUM2ULONG(limit)); } -/* compat */ +/** + * Document-method: MessagePack::Unpacker#execute + * + * call-seq: + * unpacker.execute(data, offset) -> next offset + * + * Deserializes one object over the specified buffer from _offset_ bytes. + * + * This method doesn't use the internal buffer. + * + * Call *reset()* method before calling this method again. + * + * This returns offset that was parsed to. + * Use *finished?* method to check an object is deserialized and call *data* + * method if it returns true. + * + * UnpackError is throw when parse error is occured. + */ static VALUE MessagePack_Unpacker_execute(VALUE self, VALUE data, VALUE off) { CHECK_STRING_TYPE(data); @@ -428,7 +657,16 @@ static VALUE MessagePack_Unpacker_execute(VALUE self, VALUE data, VALUE off) (size_t)NUM2ULONG(off), (size_t)RSTRING_LEN(data)); } -/* compat */ +/** + * Document-method: MessagePack::Unpacker#finished? + * + * call-seq: + * unpacker.finished? + * + * Returns true if an object is ready to get with data method. + * + * Use this method with execute method. + */ static VALUE MessagePack_Unpacker_finished_p(VALUE self) { UNPACKER(self, mp); @@ -438,14 +676,30 @@ static VALUE MessagePack_Unpacker_finished_p(VALUE self) return Qfalse; } -/* compat */ +/** + * Document-method: MessagePack::Unpacker#data + * + * call-seq: + * unpacker.data + * + * Gets the object deserialized by execute method. + * + * Use this method with execute method. + */ static VALUE MessagePack_Unpacker_data(VALUE self) { UNPACKER(self, mp); return template_data(mp); } -/* compat */ +/** + * Document-method: MessagePack::Unpacker#reset + * + * call-seq: + * unpacker.reset + * + * Resets the internal state of the unpacker. + */ static VALUE MessagePack_Unpacker_reset(VALUE self) { UNPACKER(self, mp); @@ -460,27 +714,100 @@ void Init_msgpack_unpack(VALUE mMessagePack) s_sysread = rb_intern("sysread"); s_readpartial = rb_intern("readpartial"); -#ifdef HAVE_RUBY_ENCODING_H - s_ascii_8bit = rb_enc_find_index("ASCII-8BIT"); -#endif - eUnpackError = rb_define_class_under(mMessagePack, "UnpackError", rb_eStandardError); cUnpacker = rb_define_class_under(mMessagePack, "Unpacker", rb_cObject); rb_define_alloc_func(cUnpacker, MessagePack_Unpacker_alloc); + rb_define_method(cUnpacker, "initialize", MessagePack_Unpacker_initialize, -1); + + /* Buffered API */ rb_define_method(cUnpacker, "feed", MessagePack_Unpacker_feed, 1); rb_define_method(cUnpacker, "fill", MessagePack_Unpacker_fill, 0); rb_define_method(cUnpacker, "each", MessagePack_Unpacker_each, 0); rb_define_method(cUnpacker, "stream", MessagePack_Unpacker_stream_get, 0); rb_define_method(cUnpacker, "stream=", MessagePack_Unpacker_stream_set, 1); - rb_define_module_function(mMessagePack, "unpack", MessagePack_unpack, 1); - rb_define_module_function(mMessagePack, "unpack_limit", MessagePack_unpack_limit, 2); - /* backward compatibility */ + /* Unbuffered API */ rb_define_method(cUnpacker, "execute", MessagePack_Unpacker_execute, 2); rb_define_method(cUnpacker, "execute_limit", MessagePack_Unpacker_execute_limit, 3); rb_define_method(cUnpacker, "finished?", MessagePack_Unpacker_finished_p, 0); rb_define_method(cUnpacker, "data", MessagePack_Unpacker_data, 0); rb_define_method(cUnpacker, "reset", MessagePack_Unpacker_reset, 0); + + /** + * MessagePack module is defined in rbinit.c file. + * mMessagePack = rb_define_module("MessagePack"); + */ + rb_define_module_function(mMessagePack, "unpack", MessagePack_unpack, 1); + rb_define_module_function(mMessagePack, "unpack_limit", MessagePack_unpack_limit, 2); } +/** + * Document-module: MessagePack::Unpacker + * + * Deserializer class that includes Buffered API and Unbuffered API. + * + * + * Buffered API uses the internal buffer of the Unpacker. + * Following code uses Buffered API with an input stream: + * + * # create an unpacker with input stream. + * pac = MessagePack::Unpacker.new(STDIN) + * + * # deserialize object one after another. + * pac.each {|obj| + * # ... + * } + * + * + * Following code doesn't use the input stream and feeds buffer + * manually. This is useful to use special stream or with + * event-driven I/O library. + * + * # create an unpacker without input stream. + * pac = MessagePack::Unpacker.new() + * + * # feed buffer to the internal buffer. + * pac.feed(input_bytes) + * + * # deserialize object one after another. + * pac.each {|obj| + * # ... + * } + * + * + * You can manage the buffer manually with the combination of + * *execute*, *finished?*, *data* and *reset* method. + * + * # create an unpacker. + * pac = MessagePack::Unpacker.new() + * + * # manage buffer and offset manually. + * offset = 0 + * buffer = '' + * + * # read some data into the buffer. + * buffer << [1,2,3].to_msgpack + * buffer << [4,5,6].to_msgpack + * + * while true + * offset = pac.execute(buffer, offset) + * + * if pac.finished? + * obj = pac.data + * + * buffer.slice!(0, offset) + * offset = 0 + * pac.reset + * + * # do something with the object + * # ... + * + * # repeat execution if there are more data. + * next unless buffer.empty? + * end + * + * break + * end + */ + diff --git a/ruby/unpack.h b/ruby/unpack.h index ce2a8de..91d3eb7 100644 --- a/ruby/unpack.h +++ b/ruby/unpack.h @@ -1,7 +1,7 @@ /* * MessagePack for Ruby unpacking routine * - * Copyright (C) 2008-2009 FURUHASHI Sadayuki + * Copyright (C) 2008-2010 FURUHASHI Sadayuki * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/ruby/version.rb b/ruby/version.rb new file mode 100644 index 0000000..b156620 --- /dev/null +++ b/ruby/version.rb @@ -0,0 +1,3 @@ +module MessagePack + VERSION = "0.4.3" +end |
