satt 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/satt.rb +171 -0
- metadata +45 -0
data/lib/satt.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require "msgpack"
|
2
|
+
|
3
|
+
class Satt
|
4
|
+
class InvalidArgument < RuntimeError; end
|
5
|
+
|
6
|
+
def self.dump(obj)
|
7
|
+
MessagePack.dump(Satt::Primitive::Dumper.new.dump(obj))
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.load(blob)
|
11
|
+
Satt::Primitive::Loader.new.load(MessagePack.load(blob))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Satt
|
16
|
+
class Primitive
|
17
|
+
NOT_DUMPABLE = [ Binding, IO, Proc, Class ].freeze
|
18
|
+
DONT_SERIALIZE = [ NilClass, Fixnum, Float, TrueClass, FalseClass ].freeze
|
19
|
+
OTHER_PRIMITIVES = [ Symbol, String, Array, Hash, Bignum ].freeze
|
20
|
+
|
21
|
+
class Dumper
|
22
|
+
def initialize()
|
23
|
+
@next_id = 0
|
24
|
+
@ids = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def dump(obj)
|
28
|
+
NOT_DUMPABLE.each do |cl|
|
29
|
+
raise InvalidArgument, "objects of type #{cl.to_s} are not dumpable" if obj.is_a?(cl)
|
30
|
+
end
|
31
|
+
|
32
|
+
case obj
|
33
|
+
when *DONT_SERIALIZE
|
34
|
+
obj
|
35
|
+
when Symbol, Bignum
|
36
|
+
[ class_identifier(obj), dump_value(obj) ]
|
37
|
+
else
|
38
|
+
id, cached = local_id(obj)
|
39
|
+
arr = [ class_identifier(obj), id ]
|
40
|
+
arr << dump_value(obj) unless cached
|
41
|
+
arr
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def dump_value(obj)
|
48
|
+
case obj
|
49
|
+
when String
|
50
|
+
obj
|
51
|
+
when Symbol
|
52
|
+
obj.to_s
|
53
|
+
when Bignum
|
54
|
+
obj.to_s(16)
|
55
|
+
when Array
|
56
|
+
obj.map{ |e| dump(e) }
|
57
|
+
when Hash
|
58
|
+
obj.reduce(Hash.new) do |hash, (key, value)|
|
59
|
+
hash[dump(key)] = dump(value)
|
60
|
+
hash
|
61
|
+
end
|
62
|
+
else
|
63
|
+
obj.instance_variables.inject(Hash.new) do |hash, (var, val)|
|
64
|
+
hash[var.to_s[1..-1]] = dump(obj.instance_variable_get(var))
|
65
|
+
hash
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def local_id(obj)
|
71
|
+
if @ids.key?(obj.__id__)
|
72
|
+
[ @ids[obj.__id__], true ]
|
73
|
+
else
|
74
|
+
[ @ids[obj.__id__] = (@next_id += 1), false ]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def class_identifier(obj)
|
79
|
+
if idx = OTHER_PRIMITIVES.index(obj.class)
|
80
|
+
idx
|
81
|
+
else
|
82
|
+
obj.class.to_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Loader
|
88
|
+
def initialize()
|
89
|
+
@objs = {}
|
90
|
+
end
|
91
|
+
|
92
|
+
def load(priv)
|
93
|
+
return priv if DONT_SERIALIZE.include?(priv.class)
|
94
|
+
raise InvalidArgument, priv.inspect if priv.class != Array or priv.empty?
|
95
|
+
objclass = get_class(priv.first)
|
96
|
+
|
97
|
+
if objclass == Symbol
|
98
|
+
raise InvalidArgument unless priv.length == 2 and priv.last.class == String
|
99
|
+
return priv.last.to_sym
|
100
|
+
end
|
101
|
+
|
102
|
+
if objclass == Bignum
|
103
|
+
raise InvalidArgument unless priv.length == 2 and priv.last.class == String
|
104
|
+
return priv.last.to_i(16)
|
105
|
+
end
|
106
|
+
|
107
|
+
if [Array, Hash].include?(objclass) and priv.length == 3
|
108
|
+
raise InvalidArgument unless priv.last.class == objclass
|
109
|
+
end
|
110
|
+
|
111
|
+
# TODO: allocate object immediatelly and add to cache
|
112
|
+
# even if ivars not present (but come later), error if two entries
|
113
|
+
# with same id and both with ivars
|
114
|
+
if priv.length == 2
|
115
|
+
return fetch_obj(priv[1])
|
116
|
+
end
|
117
|
+
|
118
|
+
if objclass == Array
|
119
|
+
return cache_obj(priv[1], priv[2].map{ |e| load(e) })
|
120
|
+
end
|
121
|
+
|
122
|
+
if objclass == Hash
|
123
|
+
return cache_obj(priv[1], priv[2].reduce(Hash.new) { |hash, (key, value)|
|
124
|
+
hash[load(key)] = load(value)
|
125
|
+
hash
|
126
|
+
})
|
127
|
+
end
|
128
|
+
|
129
|
+
if objclass == String
|
130
|
+
raise InvalidArgument if priv.last.class != String
|
131
|
+
return cache_obj(priv[1], priv[2])
|
132
|
+
end
|
133
|
+
|
134
|
+
if priv.last.class != Hash
|
135
|
+
raise InvalidArgument, priv.last.inspect
|
136
|
+
end
|
137
|
+
|
138
|
+
return build_and_cache_obj(objclass, priv[1], priv[2])
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def fetch_obj(ref)
|
144
|
+
return @objs[ref] if @objs[ref]
|
145
|
+
raise InvalidArgument, "can't find object with reference id #{ref.to_s}"
|
146
|
+
end
|
147
|
+
|
148
|
+
def cache_obj(ref, obj)
|
149
|
+
return @objs[ref] = obj unless @objs[ref]
|
150
|
+
raise InvalidArgument, "an object with reference id #{ref.to_s} already exists"
|
151
|
+
end
|
152
|
+
|
153
|
+
def build_and_cache_obj(objclass, ref, ivars)
|
154
|
+
# Cache first, only then go deeper, to avoid following circular references
|
155
|
+
obj = cache_obj(ref, objclass.allocate)
|
156
|
+
ivars.each do |(var, val)|
|
157
|
+
obj.instance_variable_set "@#{var}".to_sym, load(val)
|
158
|
+
end
|
159
|
+
return obj
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_class(id)
|
163
|
+
return OTHER_PRIMITIVES[id] if id.class == Fixnum
|
164
|
+
unless Object.constants.include?(id.to_sym) and objclass = Object.const_get(id) and objclass.class == Class
|
165
|
+
raise InvalidArgument, "unknown class #{id.to_s}"
|
166
|
+
end
|
167
|
+
objclass
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
metadata
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: satt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Florian Weingarten
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-24 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Serializing arbitrary Ruby objects with MessagePack
|
15
|
+
email: flo@hackvalue.de
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/satt.rb
|
21
|
+
homepage: http://rubygems.org/gems/satt
|
22
|
+
licenses: []
|
23
|
+
post_install_message:
|
24
|
+
rdoc_options: []
|
25
|
+
require_paths:
|
26
|
+
- lib
|
27
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '0'
|
32
|
+
none: false
|
33
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
none: false
|
39
|
+
requirements: []
|
40
|
+
rubyforge_project:
|
41
|
+
rubygems_version: 1.8.23
|
42
|
+
signing_key:
|
43
|
+
specification_version: 3
|
44
|
+
summary: Serialize All The Things
|
45
|
+
test_files: []
|