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