satt 0.0.1
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.
- 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: []
|