satt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/satt.rb +171 -0
  2. metadata +45 -0
@@ -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: []