rencoder 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +65 -0
- data/lib/rencoder/coder.rb +12 -0
- data/lib/rencoder/decoder.rb +122 -0
- data/lib/rencoder/encoder.rb +96 -0
- data/lib/rencoder/version.rb +3 -0
- data/lib/rencoder.rb +63 -0
- data/rencoder.gemspec +23 -0
- data/spec/rencoder/decoder_spec.rb +95 -0
- data/spec/rencoder/encoder_spec.rb +135 -0
- data/spec/spec_helper.rb +80 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2e4a5fe649e623f7058b7e081b03710bb11a16dc
|
4
|
+
data.tar.gz: f1a19bd39b9d269f72ca11d22deb1f12abbb859a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d5f773dac8f19d1409d5febdac6cb94f2360b7fb21962fcafa59c662725b955804594ccf93d802e02df950ae5a3e24423268b0cf97c59d10dcaeabc3d5179685
|
7
|
+
data.tar.gz: 716bfbe002718eac3ea92b3803e8a3426e788d3d882405d1862afd62cd0cec33a3fc4a21671cf22bd6d4d42fcaabf167662c85f857a22aeacbe52a02fbaa432d
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.4
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Igor Yamolov
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Rencoder
|
2
|
+
|
3
|
+
Rencoder is pure Ruby implementation of Rencoder serialization format encoding/decoding.
|
4
|
+
|
5
|
+
Rencoder is *FULLY* compliant with Python implementation, and uses all optimizations (by-type-offset integers, strings, arrays, hashes) both in encoding and decoding.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
### Serialization
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require 'rencoder'
|
13
|
+
|
14
|
+
Rencode.dump("Hello World") # Strings
|
15
|
+
|
16
|
+
Rencode.dump(100) # Integer
|
17
|
+
|
18
|
+
Rencoder.dump(1.0001) # Floats
|
19
|
+
|
20
|
+
Rencoder.dump({ hello: "world" }) # Hashes
|
21
|
+
|
22
|
+
Rencoder.dump(["hello", :world, 123]) # Arrays
|
23
|
+
```
|
24
|
+
|
25
|
+
**Float precion notice**
|
26
|
+
Rencoder uses 64-bit precision by default.
|
27
|
+
It's highly recommended to stay that way.
|
28
|
+
If there is strong reason to use 32-bit precision, then please specify
|
29
|
+
``float32: true`` option for ``Rencoder.dump``:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
Rencoder.dump(1.000001, float32: true)
|
33
|
+
```
|
34
|
+
***Using 32-bit precision is highly NOT recommended***
|
35
|
+
|
36
|
+
### Deserialization
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
require 'rencoder'
|
40
|
+
|
41
|
+
Rencoder.load(hash_data)
|
42
|
+
# => { 'hello': 'world' }
|
43
|
+
|
44
|
+
Rencoder.load(string_data)
|
45
|
+
# => "Hello World"
|
46
|
+
|
47
|
+
# etc
|
48
|
+
```
|
49
|
+
|
50
|
+
## Installation
|
51
|
+
|
52
|
+
Add this line to your application's Gemfile:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
gem 'rencoder'
|
56
|
+
```
|
57
|
+
|
58
|
+
And then execute:
|
59
|
+
|
60
|
+
$ bundle
|
61
|
+
|
62
|
+
Or install it yourself as:
|
63
|
+
|
64
|
+
$ gem install rencoder
|
65
|
+
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Rencoder
|
2
|
+
module Decoder
|
3
|
+
INTEGER_DECODING_MAP = {
|
4
|
+
CHR_INT1 => [1, 'c'],
|
5
|
+
CHR_INT2 => [2, 's>'],
|
6
|
+
CHR_INT4 => [4, 'l>'],
|
7
|
+
CHR_INT8 => [8, 'q>']
|
8
|
+
}
|
9
|
+
|
10
|
+
def decode(buffer)
|
11
|
+
buffer = StringIO.new(buffer) unless buffer.respond_to?(:read) # IO object
|
12
|
+
|
13
|
+
type = buffer.getbyte
|
14
|
+
|
15
|
+
case type
|
16
|
+
when STR_HEADER, STR_FIXED
|
17
|
+
decode_string(buffer, type)
|
18
|
+
when CHR_INT, CHR_INT1, CHR_INT2, CHR_INT4, CHR_INT8, INT_POS_FIXED, INT_NEG_FIXED
|
19
|
+
decode_integer(buffer, type)
|
20
|
+
when CHR_FLOAT32, CHR_FLOAT64
|
21
|
+
decode_float(buffer, type)
|
22
|
+
when CHR_TRUE, CHR_FALSE
|
23
|
+
decode_boolean(buffer, type)
|
24
|
+
when CHR_NONE
|
25
|
+
decode_nil(buffer, type)
|
26
|
+
when CHR_LIST, LIST_FIXED
|
27
|
+
decode_array(buffer, type)
|
28
|
+
when CHR_DICT, DICT_FIXED
|
29
|
+
decode_hash(buffer, type)
|
30
|
+
when CHR_TERM
|
31
|
+
:rencode_term
|
32
|
+
else
|
33
|
+
raise "Unknown type '#{type.inspect}'"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def decode_integer(buffer, type)
|
40
|
+
case type
|
41
|
+
when CHR_INT
|
42
|
+
read_till(buffer).to_i
|
43
|
+
when CHR_INT1, CHR_INT2, CHR_INT4, CHR_INT8
|
44
|
+
size, template = INTEGER_DECODING_MAP[type]
|
45
|
+
|
46
|
+
buffer.read(size).unpack(template).first
|
47
|
+
when INT_POS_FIXED
|
48
|
+
type - INT_POS_FIXED_START
|
49
|
+
when INT_NEG_FIXED
|
50
|
+
-1-(type - INT_NEG_FIXED_START)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def decode_float(buffer, type)
|
55
|
+
case type
|
56
|
+
when CHR_FLOAT32
|
57
|
+
buffer.read(4).unpack('g')
|
58
|
+
when CHR_FLOAT64
|
59
|
+
buffer.read(8).unpack('G')
|
60
|
+
end.first
|
61
|
+
end
|
62
|
+
|
63
|
+
def decode_string(buffer, type)
|
64
|
+
case type
|
65
|
+
when STR_FIXED
|
66
|
+
buffer.read(type - STR_FIXED_START)
|
67
|
+
when STR_HEADER
|
68
|
+
length = type.chr + read_till(buffer, ':')
|
69
|
+
|
70
|
+
buffer.read(length.to_i)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def decode_boolean(buffer, type)
|
75
|
+
type == CHR_TRUE
|
76
|
+
end
|
77
|
+
|
78
|
+
def decode_array(buffer, type)
|
79
|
+
case type
|
80
|
+
when CHR_LIST
|
81
|
+
result = []
|
82
|
+
|
83
|
+
while (item = decode(buffer)) != :rencode_term
|
84
|
+
result << item
|
85
|
+
end
|
86
|
+
|
87
|
+
result
|
88
|
+
when LIST_FIXED
|
89
|
+
size = type - LIST_FIXED_START
|
90
|
+
|
91
|
+
size.times.map do |i|
|
92
|
+
decode(buffer)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def decode_hash(buffer, type)
|
98
|
+
case type
|
99
|
+
when CHR_DICT
|
100
|
+
result = {}
|
101
|
+
|
102
|
+
while (key = decode(buffer)) != :rencode_term
|
103
|
+
result[key] = decode(buffer)
|
104
|
+
end
|
105
|
+
|
106
|
+
result
|
107
|
+
when DICT_FIXED
|
108
|
+
size = type - DICT_FIXED_START
|
109
|
+
|
110
|
+
Hash[size.times.map { [decode(buffer), decode(buffer)] }]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def decode_nil(buffer, type)
|
115
|
+
nil
|
116
|
+
end
|
117
|
+
|
118
|
+
def read_till(buffer, separator = CHR_TERM.chr)
|
119
|
+
buffer.gets(separator).chomp(separator)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Rencoder
|
2
|
+
module Encoder
|
3
|
+
class EncodingError < StandardError; end
|
4
|
+
|
5
|
+
def encode(object)
|
6
|
+
case object
|
7
|
+
when String, Symbol then encode_string(object)
|
8
|
+
when Fixnum then encode_integer(object)
|
9
|
+
when Float then encode_float(object)
|
10
|
+
when TrueClass, FalseClass then encode_boolean(object)
|
11
|
+
when NilClass then encode_nil(object)
|
12
|
+
when Array then encode_array(object)
|
13
|
+
when Hash then encode_hash(object)
|
14
|
+
else
|
15
|
+
raise EncodingError, "Unable to serialize '#{object.class}'"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def encode_integer(object)
|
20
|
+
case object
|
21
|
+
when (0...INT_POS_FIXED_COUNT) # predefined positive intger
|
22
|
+
[INT_POS_FIXED_START + object].pack('C')
|
23
|
+
when (-INT_NEG_FIXED_COUNT...0) # predefined negative integer
|
24
|
+
[INT_NEG_FIXED_START-1-object].pack('C')
|
25
|
+
when (-128...128)
|
26
|
+
[CHR_INT1, object].pack('Cc') # 8-bit signed
|
27
|
+
when (-32768...32768)
|
28
|
+
[CHR_INT2, object].pack('Cs>') # 16-bit signed
|
29
|
+
when (-2147483648...2147483648)
|
30
|
+
[CHR_INT4, object].pack('Cl>') # 32-bit signed
|
31
|
+
when (-9223372036854775808...9223372036854775808)
|
32
|
+
[CHR_INT8, object].pack('Cq>') # 64-bit signed
|
33
|
+
else # encode as ASCII
|
34
|
+
bytes = object.to_s.bytes
|
35
|
+
|
36
|
+
if bytes.size >= MAX_INT_LENGTH
|
37
|
+
raise EncodingError, "Unable to serialize Fixnum #{object} due to overflow"
|
38
|
+
end
|
39
|
+
|
40
|
+
[CHR_INT, *bytes, CHR_TERM].pack('C*')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def encode_float(object)
|
45
|
+
# Always serialize floats as 64-bit, since single-precision serialization is a poo
|
46
|
+
# If you don't believe me try this:
|
47
|
+
#
|
48
|
+
# [1.1].pack('F').unpack('F').first
|
49
|
+
# => 1.100000023841858
|
50
|
+
#
|
51
|
+
if options[:float32] # not recommended
|
52
|
+
[CHR_FLOAT32, object].pack('Cg')
|
53
|
+
else
|
54
|
+
[CHR_FLOAT64, object].pack('CG')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def encode_string(object)
|
59
|
+
bytes = object.to_s
|
60
|
+
|
61
|
+
if bytes.size < STR_FIXED_COUNT
|
62
|
+
(STR_FIXED_START + bytes.size).chr + bytes
|
63
|
+
else
|
64
|
+
"#{bytes.bytesize.to_s}:#{bytes}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def encode_boolean(object)
|
69
|
+
[object ? CHR_TRUE : CHR_FALSE].pack('C')
|
70
|
+
end
|
71
|
+
|
72
|
+
def encode_array(object)
|
73
|
+
array_data = object.map { |item| encode(item) }.join
|
74
|
+
|
75
|
+
if object.size < LIST_FIXED_COUNT
|
76
|
+
(LIST_FIXED_START + object.size).chr + array_data
|
77
|
+
else
|
78
|
+
CHR_LIST.chr + array_data + CHR_TERM.chr
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def encode_hash(object)
|
83
|
+
hash_data = object.map { |key, value| encode(key) + encode(value) }.join
|
84
|
+
|
85
|
+
if object.size < DICT_FIXED_COUNT
|
86
|
+
(DICT_FIXED_START + object.size).chr + hash_data
|
87
|
+
else
|
88
|
+
CHR_DICT.chr + hash_data + CHR_TERM.chr
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def encode_nil(object)
|
93
|
+
[CHR_NONE].pack('C')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/rencoder.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rencoder/version'
|
2
|
+
|
3
|
+
module Rencoder
|
4
|
+
# Rencoder Constants
|
5
|
+
MAX_INT_LENGTH = 64
|
6
|
+
|
7
|
+
# Type constants
|
8
|
+
CHR_LIST = 59
|
9
|
+
CHR_DICT = 60
|
10
|
+
CHR_INT = 61
|
11
|
+
CHR_INT1 = 62
|
12
|
+
CHR_INT2 = 63
|
13
|
+
CHR_INT4 = 64
|
14
|
+
CHR_INT8 = 65
|
15
|
+
CHR_FLOAT32 = 66
|
16
|
+
CHR_FLOAT64 = 44
|
17
|
+
CHR_TRUE = 67
|
18
|
+
CHR_FALSE = 68
|
19
|
+
CHR_NONE = 69
|
20
|
+
CHR_TERM = 127
|
21
|
+
|
22
|
+
# Dictionaries with length embedded in typecode.
|
23
|
+
DICT_FIXED_START = 102
|
24
|
+
DICT_FIXED_COUNT = 25
|
25
|
+
DICT_FIXED = (DICT_FIXED_START...DICT_FIXED_START + DICT_FIXED_COUNT)
|
26
|
+
|
27
|
+
# Positive integers with value embedded in typecode.
|
28
|
+
INT_POS_FIXED_START = 0
|
29
|
+
INT_POS_FIXED_COUNT = 44
|
30
|
+
INT_POS_FIXED = (INT_POS_FIXED_START...INT_POS_FIXED_START + INT_POS_FIXED_COUNT)
|
31
|
+
|
32
|
+
# Negative integers with value embedded in typecode.
|
33
|
+
INT_NEG_FIXED_START = 70
|
34
|
+
INT_NEG_FIXED_COUNT = 32
|
35
|
+
INT_NEG_FIXED = (INT_NEG_FIXED_START...INT_NEG_FIXED_START + INT_NEG_FIXED_COUNT)
|
36
|
+
|
37
|
+
# String length header
|
38
|
+
STR_HEADER = ('0'.ord..'9'.ord)
|
39
|
+
|
40
|
+
# Strings with length embedded in typecode.
|
41
|
+
STR_FIXED_START = 128
|
42
|
+
STR_FIXED_COUNT = 64
|
43
|
+
STR_FIXED = (STR_FIXED_START..STR_FIXED_START + STR_FIXED_COUNT)
|
44
|
+
|
45
|
+
# Lists with length embedded in typecode.
|
46
|
+
LIST_FIXED_START = STR_FIXED_START+STR_FIXED_COUNT
|
47
|
+
LIST_FIXED_COUNT = 64
|
48
|
+
LIST_FIXED = (LIST_FIXED_START..LIST_FIXED_START + LIST_FIXED_COUNT)
|
49
|
+
|
50
|
+
require_relative 'rencoder/encoder'
|
51
|
+
require_relative 'rencoder/decoder'
|
52
|
+
require_relative 'rencoder/coder'
|
53
|
+
|
54
|
+
def load(buffer, options = {})
|
55
|
+
Rencoder::Coder.new(options).decode(buffer)
|
56
|
+
end
|
57
|
+
|
58
|
+
def dump(object, options = {})
|
59
|
+
Rencoder::Coder.new(options).encode(object)
|
60
|
+
end
|
61
|
+
|
62
|
+
module_function :dump, :load
|
63
|
+
end
|
data/rencoder.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rencoder/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'rencoder'
|
8
|
+
spec.version = Rencoder::VERSION
|
9
|
+
spec.authors = ['Igor Yamolov']
|
10
|
+
spec.email = ['clouster@yandex.ru']
|
11
|
+
spec.summary = %q{Rencoder is pure Ruby implementation of Rencoder serialization format encoding/decoding.}
|
12
|
+
spec.description = %q{Rencoder is implementation of Rencoder encoding/decoding.}
|
13
|
+
spec.homepage = 'https://github.com/t3hk0d3/rencoder'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
22
|
+
spec.add_development_dependency 'rspec', '~> 3.1.0'
|
23
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rencoder::Decoder do
|
4
|
+
include_context 'serialization_values'
|
5
|
+
|
6
|
+
subject { Rencoder::Coder.new }
|
7
|
+
|
8
|
+
describe '#decode' do
|
9
|
+
describe 'string' do
|
10
|
+
it 'decode string' do
|
11
|
+
expect(subject.decode(serialized_string)).to eq('Test')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'decode long string' do
|
15
|
+
expect(subject.decode(serialized_long_string)).to eq('a' * 100)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'integer' do
|
20
|
+
it 'decode positive small integer' do
|
21
|
+
expect(subject.decode(serialized_positive_integer)).to eq(10)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'decode negative small integer' do
|
25
|
+
expect(subject.decode(serialized_negative_integer)).to eq(-10)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'decode 8-bit integer' do
|
29
|
+
expect(subject.decode(serialized_8bit_integer)).to eq(100)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'decode 16-bit integer' do
|
33
|
+
expect(subject.decode(serialized_16bit_integer)).to eq(5000)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'decode 32-bit integer' do
|
37
|
+
expect(subject.decode(serialized_32bit_integer)).to eq(50000)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'decode 64-bit integer' do
|
41
|
+
expect(subject.decode(serialized_64bit_integer)).to eq(5000000000)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'decode big ascii integer' do
|
45
|
+
expect(subject.decode(serialized_big_integer)).to eq(50000000000000000000)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'float' do
|
50
|
+
it 'decode 32-bit float' do
|
51
|
+
expect(subject.decode(serialized_32bit_float)).to eq(100.0)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'decode 64-bit float' do
|
55
|
+
expect(subject.decode(serialized_float)).to eq(100.0001)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'boolean' do
|
60
|
+
it 'decode true boolean' do
|
61
|
+
expect(subject.decode(serialized_true)).to eq(true)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'decode false boolean' do
|
65
|
+
expect(subject.decode(serialized_false)).to eq(false)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'nil' do
|
70
|
+
it 'decode nil' do
|
71
|
+
expect(subject.decode(serialized_nil)).to eq(nil)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'array' do
|
76
|
+
it 'decode small array' do
|
77
|
+
expect(subject.decode(serialized_array)).to eq(['Test', 100, 100.0001, nil])
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'decode big array' do
|
81
|
+
expect(subject.decode(serialized_big_array)).to eq(100.times.to_a)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'hash' do
|
86
|
+
it 'decode small hash' do
|
87
|
+
expect(subject.decode(serialized_hash)).to eq({ 'test' => 123, 'hello' => 'world' })
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'decode big hash' do
|
91
|
+
expect(subject.decode(serialized_big_hash)).to eq(Hash[100.times.map { |i| [i, i.chr] }])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rencoder::Encoder do
|
4
|
+
include_context 'serialization_values'
|
5
|
+
|
6
|
+
subject { Rencoder::Coder.new }
|
7
|
+
|
8
|
+
describe '#encode' do
|
9
|
+
it 'encode string' do
|
10
|
+
expect(subject.encode('Test')).to eq(serialized_string)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'encode symbol' do
|
14
|
+
expect(subject.encode(:test)).to eq(serialized_symbol)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'encode integer' do
|
18
|
+
expect(subject.encode(100)).to eq(serialized_integer)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'encode float' do
|
22
|
+
expect(subject.encode(100.0001)).to eq(serialized_float)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'encode boolean' do
|
26
|
+
expect(subject.encode(false)).to eq(serialized_false)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'encode nil' do
|
30
|
+
expect(subject.encode(nil)).to eq(serialized_nil)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'encode array' do
|
34
|
+
expect(subject.encode(["Test", 100, 100.0001, nil])).to eq(serialized_array)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'encode hash' do
|
38
|
+
expect(subject.encode({ test: 123, hello: 'world' })).to eq(serialized_hash)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'raise exception for non-serializable object' do
|
42
|
+
expect { subject.encode(Object.new) }.to raise_error(Rencoder::Encoder::EncodingError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#encode_string' do
|
47
|
+
it 'encode small strings' do
|
48
|
+
expect(subject.encode_string('Test')).to eq(serialized_string)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'encode big strings' do
|
52
|
+
expect(subject.encode_string('a' * 100)).to eq(serialized_long_string)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#encode_integer' do
|
57
|
+
it 'encode small positive number' do
|
58
|
+
expect(subject.encode_integer(10)).to eq(serialized_positive_integer)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'encode small negative number' do
|
62
|
+
expect(subject.encode_integer(-10)).to eq(serialized_negative_integer)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'encode 8-bit integer' do
|
66
|
+
expect(subject.encode_integer(100)).to eq(serialized_8bit_integer)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'encode 16-bit integer' do
|
70
|
+
expect(subject.encode_integer(5000)).to eq(serialized_16bit_integer)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'encode 32-bit integer' do
|
74
|
+
expect(subject.encode_integer(50000)).to eq(serialized_32bit_integer)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'encode 64-bit integer' do
|
78
|
+
expect(subject.encode_integer(5000000000)).to eq(serialized_64bit_integer)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'encode big integer as ascii' do
|
82
|
+
expect(subject.encode_integer(50000000000000000000)).to eq(serialized_big_integer)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'raise error for super-big integers' do
|
86
|
+
expect do
|
87
|
+
subject.encode_integer(128.times.map { '9' }.join.to_i)
|
88
|
+
end.to raise_error(Rencoder::Encoder::EncodingError)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#encode_float' do
|
93
|
+
it 'encode 64-bit float' do
|
94
|
+
expect(subject.encode_float(100.0001)).to eq(serialized_float)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#encode_boolean' do
|
99
|
+
it 'encode true boolean' do
|
100
|
+
expect(subject.encode_boolean(true)).to eq(serialized_true)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'encode false boolean' do
|
104
|
+
expect(subject.encode_boolean(false)).to eq(serialized_false)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#encode_nil' do
|
109
|
+
it 'encode nil' do
|
110
|
+
expect(subject.encode_nil(nil)).to eq(serialized_nil)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '#encode_array' do
|
115
|
+
it 'encode small-sized array' do
|
116
|
+
expect(subject.encode_array(["Test", 100, 100.0001, nil])).to eq(serialized_array)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'encode big-sized array' do
|
120
|
+
expect(subject.encode_array(100.times.to_a)).to eq(serialized_big_array)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '#encode_hash' do
|
125
|
+
it 'encode small-sized hash' do
|
126
|
+
expect(subject.encode_hash({ test: 123, hello: 'world' })).to eq(serialized_hash)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'encode big-sized hash' do
|
130
|
+
hash = Hash[100.times.map { |i| [i, i.chr] }]
|
131
|
+
|
132
|
+
expect(subject.encode_hash(hash)).to eq(serialized_big_hash)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'rencoder'
|
5
|
+
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
shared_context 'serialization_values' do
|
9
|
+
|
10
|
+
# Values encoded using original Python code
|
11
|
+
|
12
|
+
# Integers
|
13
|
+
# 100
|
14
|
+
let(:serialized_integer) { '>d' }
|
15
|
+
|
16
|
+
# 10
|
17
|
+
let(:serialized_positive_integer) { "\n" }
|
18
|
+
|
19
|
+
# -10
|
20
|
+
let(:serialized_negative_integer) { 'O' }
|
21
|
+
|
22
|
+
# 100
|
23
|
+
let(:serialized_8bit_integer) { serialized_integer }
|
24
|
+
|
25
|
+
# 5000
|
26
|
+
let(:serialized_16bit_integer) { Base64.decode64('PxOI') }
|
27
|
+
|
28
|
+
# 50000
|
29
|
+
let(:serialized_32bit_integer) { Base64.decode64('QAAAw1A=') }
|
30
|
+
|
31
|
+
# 5000000000
|
32
|
+
let(:serialized_64bit_integer) { Base64.decode64('QQAAAAEqBfIA') }
|
33
|
+
|
34
|
+
# 50000000000000000000
|
35
|
+
let(:serialized_big_integer) { "=50000000000000000000\x7f" }
|
36
|
+
|
37
|
+
# Strings
|
38
|
+
# 'Test'
|
39
|
+
let(:serialized_string) { Base64.decode64('hFRlc3Q=') }
|
40
|
+
|
41
|
+
# :test (symbol)
|
42
|
+
let(:serialized_symbol) { Base64.decode64('hHRlc3Q=') }
|
43
|
+
|
44
|
+
# 'a' * 100
|
45
|
+
let(:serialized_long_string) { '100:' + 'a' * 100 }
|
46
|
+
|
47
|
+
# Booleans
|
48
|
+
# true
|
49
|
+
let(:serialized_true) { 67.chr }
|
50
|
+
|
51
|
+
let(:serialized_false) { 68.chr }
|
52
|
+
|
53
|
+
# Floats
|
54
|
+
# 100.0001
|
55
|
+
let(:serialized_float) { Base64.decode64('LEBZAAGjbi6y') }
|
56
|
+
|
57
|
+
# 100.0001 (single-precision)
|
58
|
+
let(:serialized_32bit_float) { Base64.decode64('QkLIAAA=') }
|
59
|
+
|
60
|
+
# NULL
|
61
|
+
let(:serialized_nil) { 69.chr }
|
62
|
+
|
63
|
+
# Array
|
64
|
+
# ["Test", 100, 100.0001, nil]
|
65
|
+
let(:serialized_array) { Base64.decode64('xIRUZXN0PmQsQFkAAaNuLrJF') }
|
66
|
+
|
67
|
+
# big array (100.times.to_a)
|
68
|
+
let(:serialized_big_array) do
|
69
|
+
Base64.decode64('OwABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorPiw+LT4uPi8+MD4xPjI+Mz40PjU+Nj43Pjg+OT46Pjs+PD49Pj4+Pz5APkE+Qj5DPkQ+RT5GPkc+SD5JPko+Sz5MPk0+Tj5PPlA+UT5SPlM+VD5VPlY+Vz5YPlk+Wj5bPlw+XT5ePl8+YD5hPmI+Y38=')
|
70
|
+
end
|
71
|
+
|
72
|
+
# Hash
|
73
|
+
# { test: 123, hello: "world" }
|
74
|
+
let(:serialized_hash) { Base64.decode64('aIR0ZXN0PnuFaGVsbG+Fd29ybGQ=') }
|
75
|
+
|
76
|
+
# big hash (Hash[100.times.map { |i| [i, i.chr] }])
|
77
|
+
let(:serialized_big_hash) do
|
78
|
+
Base64.decode64('PACBAAGBAQKBAgOBAwSBBAWBBQaBBgeBBwiBCAmBCQqBCguBCwyBDA2BDQ6BDg+BDxCBEBGBERKBEhOBExSBFBWBFRaBFheBFxiBGBmBGRqBGhuBGxyBHB2BHR6BHh+BHyCBICGBISKBIiOBIySBJCWBJSaBJieBJyiBKCmBKSqBKiuBKz4sgSw+LYEtPi6BLj4vgS8+MIEwPjGBMT4ygTI+M4EzPjSBND41gTU+NoE2PjeBNz44gTg+OYE5PjqBOj47gTs+PIE8Pj2BPT4+gT4+P4E/PkCBQD5BgUE+QoFCPkOBQz5EgUQ+RYFFPkaBRj5HgUc+SIFIPkmBST5KgUo+S4FLPkyBTD5NgU0+ToFOPk+BTz5QgVA+UYFRPlKBUj5TgVM+VIFUPlWBVT5WgVY+V4FXPliBWD5ZgVk+WoFaPluBWz5cgVw+XYFdPl6BXj5fgV8+YIFgPmGBYT5igWI+Y4Fjfw==')
|
79
|
+
end
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rencoder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Igor Yamolov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.1.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.1.0
|
41
|
+
description: Rencoder is implementation of Rencoder encoding/decoding.
|
42
|
+
email:
|
43
|
+
- clouster@yandex.ru
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".ruby-version"
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- lib/rencoder.rb
|
54
|
+
- lib/rencoder/coder.rb
|
55
|
+
- lib/rencoder/decoder.rb
|
56
|
+
- lib/rencoder/encoder.rb
|
57
|
+
- lib/rencoder/version.rb
|
58
|
+
- rencoder.gemspec
|
59
|
+
- spec/rencoder/decoder_spec.rb
|
60
|
+
- spec/rencoder/encoder_spec.rb
|
61
|
+
- spec/spec_helper.rb
|
62
|
+
homepage: https://github.com/t3hk0d3/rencoder
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 2.2.2
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: Rencoder is pure Ruby implementation of Rencoder serialization format encoding/decoding.
|
86
|
+
test_files:
|
87
|
+
- spec/rencoder/decoder_spec.rb
|
88
|
+
- spec/rencoder/encoder_spec.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
has_rdoc:
|