transit-ruby 0.8.467

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.
@@ -0,0 +1,406 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Transit
16
+ # WriteHandlers convert instances of Ruby types to their
17
+ # corresponding Transit semantic types, and ReadHandlers read
18
+ # convert transit values back into instances of Ruby
19
+ # types. transit-ruby ships with default sets of WriteHandlers for
20
+ # each of the Ruby types that map naturally to transit types, and
21
+ # ReadHandlers for each transit type. For the common case, the
22
+ # built-in handlers will suffice, but you can add your own extension
23
+ # types and/or override the built-in handlers.
24
+ #
25
+ # ## Custom handlers
26
+ #
27
+ # For example, Ruby has Date, Time, and DateTime, each with their
28
+ # own semantics. Transit has an instance type, which does not
29
+ # differentiate between Date and Time, so transit-ruby writes Dates,
30
+ # Times, and DateTimes as transit instances, and reads transit
31
+ # instances as DateTimes. If your application cares that Dates are
32
+ # different from DateTimes, you could register custom write and read
33
+ # handlers, overriding the built-in DateHandler and adding a new
34
+ # DateReadHandler.
35
+ #
36
+ # ### Write handlers
37
+ #
38
+ # Write handlers are required to expose <tt>tag</tt>, <tt>rep</tt>, and <tt>string_rep</tt> methods:
39
+ #
40
+ # ```ruby
41
+ # class DateWriteHandler
42
+ # def tag(_) "D" end
43
+ # def rep(o) o.to_s end
44
+ # def string_rep(o) o.to_s end
45
+ # def verbose_handler(_) nil end # optional - see Verbose write handlers, below
46
+ # end
47
+ # ```
48
+ #
49
+ # <tt>tag</tt> returns the tag used to identify the transit type
50
+ # (built-in or extension). It accepts the object being written,
51
+ # which allows the handler to return different tags for different
52
+ # semantics, e.g. the built-in IntHandler, which returns the tag "i"
53
+ # for numbers that fit within a 64-bit signed integer and "n" for
54
+ # anything outside that range.
55
+ #
56
+ # <tt>rep</tt> accepts the object being written and returns its wire
57
+ # representation. This can be a scalar value (identified by a
58
+ # one-character tag) or a map (Ruby Hash) or an array (identified by
59
+ # a multi-character tag).
60
+ #
61
+ # <tt>string_rep</tt> accepts the object being written and returns a
62
+ # string representation. Used when the object is a key in a map.
63
+ #
64
+ # ### Read handlers
65
+ #
66
+ # Read handlers are required to expose a single <tt>from_rep</tt> method:
67
+ #
68
+ # ```ruby
69
+ # class DateReadHandler
70
+ # def from_rep(rep)
71
+ # Date.parse(rep)
72
+ # end
73
+ # end
74
+ # ```
75
+ #
76
+ # <tt>from_rep</tt> accepts the wire representation (without the tag), and
77
+ # uses it to build an appropriate Ruby object.
78
+ #
79
+ # ### Usage
80
+ #
81
+ # ```ruby
82
+ # io = StringIO.new('','w+')
83
+ # writer = Transit::Writer.new(:json, io, :handlers => {Date => DateWriteHandler.new})
84
+ # writer.write(Date.new(2014,7,22))
85
+ # io.string
86
+ # # => "[\"~#'\",\"~D2014-07-22\"]\n"
87
+ #
88
+ # reader = Transit::Reader.new(:json, StringIO.new(io.string), :handlers => {"D" => DateReadHandler.new})
89
+ # reader.read
90
+ # # => #<Date: 2014-07-22 ((2456861j,0s,0n),+0s,2299161j)>
91
+ # ```
92
+ #
93
+ # ## Custom types and representations
94
+ #
95
+ # Transit supports scalar and structured representations. The Date
96
+ # example, above, demonstrates a String representation (scalar) of a
97
+ # Date. This works well because it is a natural representation, but
98
+ # it might not be a good solution for a more complex type, e.g. a
99
+ # Point. While you _could_ represent a Point as a String, e.g.
100
+ # <tt>("x:37,y:42")</tt>, it would be more efficient and arguably
101
+ # more natural to represent it as an array of Integers:
102
+ #
103
+ # ```ruby
104
+ # require 'ostruct'
105
+ # Point = Struct.new(:x,:y) do
106
+ # def to_a; [x,y] end
107
+ # end
108
+ #
109
+ # class PointWriteHandler
110
+ # def tag(_) "point" end
111
+ # def rep(o) o.to_a end
112
+ # def string_rep(_) nil end
113
+ # end
114
+ #
115
+ # class PointReadHandler
116
+ # def from_rep(rep)
117
+ # Point.new(*rep)
118
+ # end
119
+ # end
120
+ #
121
+ # io = StringIO.new('','w+')
122
+ # writer = Transit::Writer.new(:json_verbose, io, :handlers => {Point => PointWriteHandler.new})
123
+ # writer.write(Point.new(37,42))
124
+ # io.string
125
+ # # => "{\"~#point\":[37,42]}\n"
126
+ #
127
+ # reader = Transit::Reader.new(:json, StringIO.new(io.string),
128
+ # :handlers => {"point" => PointReadHandler.new})
129
+ # reader.read
130
+ # # => #<struct Point x=37, y=42>
131
+ # ```
132
+ #
133
+ # Note that Date used a one-character tag, "D", whereas Point uses a
134
+ # multi-character tag, "point". Transit expects one-character tags
135
+ # to have scalar representations (string, integer, float, boolean,
136
+ # etc) and multi-character tags to have structural representations,
137
+ # i.e. maps (Ruby Hashes) or arrays.
138
+ #
139
+ # ## Verbose write handlers
140
+ #
141
+ # Write handlers can, optionally, support the JSON-VERBOSE format by
142
+ # providing a verbose write handler. Transit uses this for instances
143
+ # (Ruby Dates, Times, DateTimes) to differentiate between the more
144
+ # efficient format using an int representing milliseconds since 1970
145
+ # in JSON mode from the more readable format using a String in
146
+ # JSON-VERBOSE mode.
147
+ #
148
+ # ```ruby
149
+ # inst = DateTime.new(1985,04,12,23,20,50,"0")
150
+ #
151
+ # io = StringIO.new('','w+')
152
+ # writer = Transit::Writer.new(:json, io)
153
+ # writer.write(inst)
154
+ # io.string
155
+ # #=> "[\"~#'\",\"~m482196050000\"]\n"
156
+ #
157
+ # io = StringIO.new('','w+')
158
+ # writer = Transit::Writer.new(:json_verbose, io)
159
+ # writer.write(inst)
160
+ # io.string
161
+ # #=> "{\"~#'\":\"~t1985-04-12T23:20:50.000Z\"}\n"
162
+ # ```
163
+ #
164
+ # When you want a more human-readable format for your own custom
165
+ # types in JSON-VERBOSE mode, create a second write handler and add
166
+ # a <tt>verbose_handler</tt> method to the first handler that
167
+ # returns an instance of the verbose handler:
168
+ #
169
+ # ```ruby
170
+ # Element = Struct.new(:id, :name)
171
+ #
172
+ # class ElementWriteHandler
173
+ # def tag(_) "el" end
174
+ # def rep(v) v.id end
175
+ # def string_rep(v) v.name end
176
+ # def verbose_handler() ElementVerboseWriteHandler.new end
177
+ # end
178
+ #
179
+ # class ElementVerboseWriteHandler < ElementWriteHandler
180
+ # def rep(v) v.name end
181
+ # end
182
+ #
183
+ # write_handlers = {Element => ElementWriteHandler.new}
184
+ #
185
+ # e = Element.new(3, "Lithium")
186
+ #
187
+ # io = StringIO.new('','w+')
188
+ # writer = Transit::Writer.new(:json, io, :handlers => write_handlers)
189
+ # writer.write(e)
190
+ # io.string
191
+ # # => "[\"~#el\",3]\n"
192
+ #
193
+ # io = StringIO.new('','w+')
194
+ # writer = Transit::Writer.new(:json_verbose, io, :handlers => write_handlers)
195
+ # writer.write(e)
196
+ # io.string
197
+ # # => "{\"~#el\":\"Lithium\"}\n"
198
+ # ```
199
+ #
200
+ # Note that you register the same handler collection; transit-ruby takes care of
201
+ # asking for the verbose_handler for the :json_verbose format.
202
+ module WriteHandlers
203
+ class NilHandler
204
+ def tag(_) "_" end
205
+ def rep(_) nil end
206
+ def string_rep(n) nil end
207
+ end
208
+
209
+ class KeywordHandler
210
+ def tag(_) ":" end
211
+ def rep(s) s.to_s end
212
+ def string_rep(s) rep(s) end
213
+ end
214
+
215
+ class StringHandler
216
+ def tag(_) "s" end
217
+ def rep(s) s end
218
+ def string_rep(s) s end
219
+ end
220
+
221
+ class TrueHandler
222
+ def tag(_) "?" end
223
+ def rep(_) true end
224
+ def string_rep(_) "t" end
225
+ end
226
+
227
+ class FalseHandler
228
+ def tag(_) "?" end
229
+ def rep(_) false end
230
+ def string_rep(_) "f" end
231
+ end
232
+
233
+ class IntHandler
234
+ def tag(i) i > MAX_INT || i < MIN_INT ? "n" : "i" end
235
+ def rep(i) i > MAX_INT || i < MIN_INT ? i.to_s : i end
236
+ def string_rep(i) i.to_s end
237
+ end
238
+
239
+ class FloatHandler
240
+ def tag(_) "d" end
241
+ def rep(f) f end
242
+ def string_rep(f) f.to_s end
243
+ end
244
+
245
+ class BigDecimalHandler
246
+ def tag(_) "f" end
247
+ def rep(f) f.to_s("f") end
248
+ def string_rep(f) rep(f) end
249
+ end
250
+
251
+ # TimeHandler, DateTimeHandler, and DateHandler all have different
252
+ # implementations of string_rep. Here is the rationale:
253
+ #
254
+ # For all three, want to write out the same format
255
+ # e.g. 2014-04-18T18:51:29.478Z, and we want the milliseconds to truncate
256
+ # rather than round, eg 29.4786 seconds should be 29.478, not 29.479.
257
+ # - "sss is the number of complete milliseconds since the start of the
258
+ # second as three decimal digits."
259
+ # - http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
260
+ #
261
+ # Some data points (see benchmarks/encoding_time.rb)
262
+ # - Time and DateTime each offer iso8601 methods, but strftime is faster.
263
+ # - DateTime's strftime (and iso8601) round millis
264
+ # - Time's strftime (and iso8601) truncate millis
265
+ # - we don't care about truncate v round for dates (which have 000 ms)
266
+ # - date.to_datetime.strftime(...) is considerably faster than date.to_time.strftime(...)
267
+ class TimeHandler
268
+ def tag(_) "m" end
269
+ def rep(t) DateTimeUtil.to_millis(t) end
270
+ def string_rep(t) rep(t).to_s end
271
+ def verbose_handler() VerboseTimeHandler.new end
272
+ end
273
+
274
+ class DateTimeHandler < TimeHandler
275
+ def verbose_handler() VerboseDateTimeHandler.new end
276
+ end
277
+
278
+ class DateHandler < TimeHandler
279
+ def verbose_handler() VerboseDateHandler.new end
280
+ end
281
+
282
+ class VerboseTimeHandler
283
+ def tag(_) "t" end
284
+ def rep(t)
285
+ # .getutc because we don't want to modify t
286
+ t.getutc.strftime(Transit::TIME_FORMAT)
287
+ end
288
+ def string_rep(t) rep(t) end
289
+ end
290
+
291
+ class VerboseDateTimeHandler < VerboseTimeHandler
292
+ def rep(t)
293
+ # .utc because to_time already creates a new object
294
+ t.to_time.utc.strftime(Transit::TIME_FORMAT)
295
+ end
296
+ end
297
+
298
+ class VerboseDateHandler < VerboseTimeHandler
299
+ def rep(d)
300
+ # to_datetime because DateTime's strftime is faster
301
+ # thank Time's, and millis are 000 so it doesn't matter
302
+ # if we truncate or round.
303
+ d.to_datetime.strftime(Transit::TIME_FORMAT)
304
+ end
305
+ end
306
+
307
+ class UuidHandler
308
+ def tag(_) "u" end
309
+ def rep(u) [u.most_significant_bits, u.least_significant_bits] end
310
+ def string_rep(u) u.to_s end
311
+ end
312
+
313
+ class LinkHandler
314
+ def tag(_) "link" end
315
+ def rep(l) l.to_h end
316
+ def string_rep(_) nil end
317
+ end
318
+
319
+ class UriHandler
320
+ def tag(_) "r" end
321
+ def rep(u) u.to_s end
322
+ def string_rep(u) rep(u) end
323
+ end
324
+
325
+ class AddressableUriHandler
326
+ def tag(_) "r" end
327
+ def rep(u) u.to_s end
328
+ def string_rep(u) rep(u) end
329
+ end
330
+
331
+ class ByteArrayHandler
332
+ def tag(_) "b" end
333
+ def rep(b) b.to_base64 end
334
+ def string_rep(b) rep(b) end
335
+ end
336
+
337
+ class TransitSymbolHandler
338
+ def tag(_) "$" end
339
+ def rep(s) s.to_s end
340
+ def string_rep(s) rep(s) end
341
+ end
342
+
343
+ class ArrayHandler
344
+ def tag(_) "array" end
345
+ def rep(a) a end
346
+ def string_rep(_) nil end
347
+ end
348
+
349
+ class MapHandler
350
+ def handlers=(handlers)
351
+ @handlers = handlers
352
+ end
353
+
354
+ def stringable_keys?(m)
355
+ m.keys.all? {|k| (@handlers[k.class].tag(k).length == 1) }
356
+ end
357
+
358
+ def tag(m)
359
+ stringable_keys?(m) ? "map" : "cmap"
360
+ end
361
+
362
+ def rep(m)
363
+ stringable_keys?(m) ? m : m.reduce([]) {|a, kv| a.concat(kv)}
364
+ end
365
+
366
+ def string_rep(_) nil end
367
+ end
368
+
369
+ class SetHandler
370
+ def tag(_) "set" end
371
+ def rep(s) s.to_a end
372
+ def string_rep(_) nil end
373
+ end
374
+
375
+ class TaggedValueHandler
376
+ def tag(tv) tv.tag end
377
+ def rep(tv) tv.rep end
378
+ def string_rep(_) nil end
379
+ end
380
+
381
+ DEFAULT_WRITE_HANDLERS = {
382
+ NilClass => NilHandler.new,
383
+ ::Symbol => KeywordHandler.new,
384
+ String => StringHandler.new,
385
+ TrueClass => TrueHandler.new,
386
+ FalseClass => FalseHandler.new,
387
+ Fixnum => IntHandler.new,
388
+ Bignum => IntHandler.new,
389
+ Float => FloatHandler.new,
390
+ BigDecimal => BigDecimalHandler.new,
391
+ Time => TimeHandler.new,
392
+ DateTime => DateTimeHandler.new,
393
+ Date => DateHandler.new,
394
+ UUID => UuidHandler.new,
395
+ Link => LinkHandler.new,
396
+ URI => UriHandler.new,
397
+ Addressable::URI => AddressableUriHandler.new,
398
+ ByteArray => ByteArrayHandler.new,
399
+ Transit::Symbol => TransitSymbolHandler.new,
400
+ Array => ArrayHandler.new,
401
+ Hash => MapHandler.new,
402
+ Set => SetHandler.new,
403
+ TaggedValue => TaggedValueHandler.new
404
+ }.freeze
405
+ end
406
+ end
@@ -0,0 +1,346 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Transit
16
+ # Transit::Writer marshals Ruby objects as transit values to an output stream.
17
+ # @see https://github.com/cognitect/transit-format
18
+ class Writer
19
+
20
+ # @api private
21
+ class Marshaler
22
+ def initialize(opts)
23
+ @cache_enabled = !opts[:verbose]
24
+ @prefer_strings = opts[:prefer_strings]
25
+ @max_int = opts[:max_int]
26
+ @min_int = opts[:min_int]
27
+
28
+ handlers = WriteHandlers::DEFAULT_WRITE_HANDLERS.dup
29
+ handlers = handlers.merge!(opts[:handlers]) if opts[:handlers]
30
+ @handlers = (opts[:verbose] ? verbose_handlers(handlers) : handlers)
31
+ @handlers.values.each do |h|
32
+ if h.respond_to?(:handlers=)
33
+ h.handlers=(@handlers)
34
+ end
35
+ end
36
+ end
37
+
38
+ def find_handler(obj)
39
+ obj.class.ancestors.each do |a|
40
+ if handler = @handlers[a]
41
+ return handler
42
+ end
43
+ end
44
+ nil
45
+ end
46
+
47
+ def verbose_handlers(handlers)
48
+ handlers.each do |k, v|
49
+ if v.respond_to?(:verbose_handler) && vh = v.verbose_handler
50
+ handlers.store(k, vh)
51
+ end
52
+ end
53
+ handlers
54
+ end
55
+
56
+ def escape(s)
57
+ if s.start_with?(SUB,ESC,RES) && s != "#{SUB} "
58
+ "#{ESC}#{s}"
59
+ else
60
+ s
61
+ end
62
+ end
63
+
64
+ def emit_nil(as_map_key, cache)
65
+ as_map_key ? emit_string(ESC, "_", nil, true, cache) : emit_value(nil)
66
+ end
67
+
68
+ def emit_string(prefix, tag, value, as_map_key, cache)
69
+ encoded = "#{prefix}#{tag}#{value}"
70
+ if @cache_enabled && cache.cacheable?(encoded, as_map_key)
71
+ emit_value(cache.write(encoded), as_map_key)
72
+ else
73
+ emit_value(encoded, as_map_key)
74
+ end
75
+ end
76
+
77
+ def emit_boolean(handler, b, as_map_key, cache)
78
+ as_map_key ? emit_string(ESC, "?", handler.string_rep(b), true, cache) : emit_value(b)
79
+ end
80
+
81
+ def emit_int(tag, i, as_map_key, cache)
82
+ if as_map_key || i > @max_int || i < @min_int
83
+ emit_string(ESC, tag, i, as_map_key, cache)
84
+ else
85
+ emit_value(i, as_map_key)
86
+ end
87
+ end
88
+
89
+ def emit_double(d, as_map_key, cache)
90
+ as_map_key ? emit_string(ESC, "d", d, true, cache) : emit_value(d)
91
+ end
92
+
93
+ def emit_array(a, cache)
94
+ emit_array_start(a.size)
95
+ a.each {|e| marshal(e, false, cache)}
96
+ emit_array_end
97
+ end
98
+
99
+ def emit_map(m, cache)
100
+ emit_map_start(m.size)
101
+ m.each do |k,v|
102
+ marshal(k, true, cache)
103
+ marshal(v, false, cache)
104
+ end
105
+ emit_map_end
106
+ end
107
+
108
+ def emit_tagged_value(tag, rep, cache)
109
+ emit_array_start(2)
110
+ emit_string(ESC, "#", tag, false, cache)
111
+ marshal(rep, false, cache)
112
+ emit_array_end
113
+ end
114
+
115
+ def emit_encoded(handler, tag, obj, as_map_key, cache)
116
+ if tag.length == 1
117
+ rep = handler.rep(obj)
118
+ if String === rep
119
+ emit_string(ESC, tag, rep, as_map_key, cache)
120
+ elsif as_map_key || @prefer_strings
121
+ if str_rep = handler.string_rep(obj)
122
+ emit_string(ESC, tag, str_rep, as_map_key, cache)
123
+ else
124
+ raise "Cannot be encoded as String: " + {:tag => tag, :rep => rep, :obj => obj}.to_s
125
+ end
126
+ else
127
+ emit_tagged_value(tag, handler.rep(obj), cache)
128
+ end
129
+ elsif as_map_key
130
+ raise "Cannot be used as a map key: " + {:tag => tag, :rep => rep, :obj => obj}.to_s
131
+ else
132
+ emit_tagged_value(tag, handler.rep(obj), cache)
133
+ end
134
+ end
135
+
136
+ def marshal(obj, as_map_key, cache)
137
+ handler = find_handler(obj)
138
+ tag = handler.tag(obj)
139
+ case tag
140
+ when "_"
141
+ emit_nil(as_map_key, cache)
142
+ when "?"
143
+ emit_boolean(handler, obj, as_map_key, cache)
144
+ when "s"
145
+ emit_string(nil, nil, escape(handler.rep(obj)), as_map_key, cache)
146
+ when "i"
147
+ emit_int(tag, handler.rep(obj), as_map_key, cache)
148
+ when "d"
149
+ emit_double(handler.rep(obj), as_map_key, cache)
150
+ when "'"
151
+ emit_tagged_value(tag, handler.rep(obj), cache)
152
+ when "array"
153
+ emit_array(handler.rep(obj), cache)
154
+ when "map"
155
+ emit_map(handler.rep(obj), cache)
156
+ else
157
+ emit_encoded(handler, tag, obj, as_map_key, cache)
158
+ end
159
+ end
160
+
161
+ def marshal_top(obj, cache=RollingCache.new)
162
+ if handler = find_handler(obj)
163
+ if tag = handler.tag(obj)
164
+ if tag.length == 1
165
+ marshal(TaggedValue.new(QUOTE, obj), false, cache)
166
+ else
167
+ marshal(obj, false, cache)
168
+ end
169
+ flush
170
+ else
171
+ raise "Handler must provide a non-nil tag: #{handler.inspect}"
172
+ end
173
+ else
174
+ raise "Can not find a Write Handler for #{obj.inspect}."
175
+ end
176
+ end
177
+ end
178
+
179
+ # @api private
180
+ class BaseJsonMarshaler < Marshaler
181
+ def default_opts
182
+ {:prefer_strings => true,
183
+ :max_int => JSON_MAX_INT,
184
+ :min_int => JSON_MIN_INT}
185
+ end
186
+
187
+ def initialize(io, opts)
188
+ @oj = Oj::StreamWriter.new(io)
189
+ super(default_opts.merge(opts))
190
+ @state = []
191
+ end
192
+
193
+ def emit_array_start(size)
194
+ @state << :array
195
+ @oj.push_array
196
+ end
197
+
198
+ def emit_array_end
199
+ @state.pop
200
+ @oj.pop
201
+ end
202
+
203
+ def emit_map_start(size)
204
+ @state << :map
205
+ @oj.push_object
206
+ end
207
+
208
+ def emit_map_end
209
+ @state.pop
210
+ @oj.pop
211
+ end
212
+
213
+ def emit_value(obj, as_map_key=false)
214
+ if @state.last == :array
215
+ @oj.push_value(obj)
216
+ else
217
+ as_map_key ? @oj.push_key(obj) : @oj.push_value(obj)
218
+ end
219
+ end
220
+
221
+ def flush
222
+ # no-op
223
+ end
224
+ end
225
+
226
+ # @api private
227
+ class JsonMarshaler < BaseJsonMarshaler
228
+ def emit_map(m, cache)
229
+ emit_array_start(-1)
230
+ emit_value("^ ", false)
231
+ m.each do |k,v|
232
+ marshal(k, true, cache)
233
+ marshal(v, false, cache)
234
+ end
235
+ emit_array_end
236
+ end
237
+ end
238
+
239
+ # @api private
240
+ class VerboseJsonMarshaler < BaseJsonMarshaler
241
+ def emit_string(prefix, tag, value, as_map_key, cache)
242
+ emit_value("#{prefix}#{tag}#{value}", as_map_key)
243
+ end
244
+
245
+ def emit_tagged_value(tag, rep, cache)
246
+ emit_map_start(1)
247
+ emit_string(ESC, "#", tag, true, cache)
248
+ marshal(rep, false, cache)
249
+ emit_map_end
250
+ end
251
+ end
252
+
253
+ # @api private
254
+ class MessagePackMarshaler < Marshaler
255
+ def default_opts
256
+ {:prefer_strings => false,
257
+ :max_int => MAX_INT,
258
+ :min_int => MIN_INT}
259
+ end
260
+
261
+ def initialize(io, opts)
262
+ @io = io
263
+ @packer = MessagePack::Packer.new(io)
264
+ super(default_opts.merge(opts))
265
+ end
266
+
267
+ def emit_array_start(size)
268
+ @packer.write_array_header(size)
269
+ end
270
+
271
+ def emit_array_end
272
+ # no-op
273
+ end
274
+
275
+ def emit_map_start(size)
276
+ @packer.write_map_header(size)
277
+ end
278
+
279
+ def emit_map_end
280
+ # no-op
281
+ end
282
+
283
+ def emit_value(obj, as_map_key=:ignore)
284
+ @packer.write(obj)
285
+ end
286
+
287
+ def flush
288
+ @packer.flush
289
+ @io.flush
290
+ end
291
+ end
292
+
293
+ # @param [Symbol] format required :json, :json_verbose, or :msgpack
294
+ # @param [IO] io required
295
+ # @param [Hash] opts optional
296
+ #
297
+ # Creates a new Writer configured to write to <tt>io</tt> in
298
+ # <tt>format</tt> (<tt>:json</tt>, <tt>:json_verbose</tt>,
299
+ # <tt>:msgpack</tt>).
300
+ #
301
+ # Use opts to register custom write handlers, associating each one
302
+ # with its type.
303
+ #
304
+ # @example
305
+ # json_writer = Transit::Writer.new(:json, io)
306
+ # json_verbose_writer = Transit::Writer.new(:json_verbose, io)
307
+ # msgpack_writer = Transit::Writer.new(:msgpack, io)
308
+ # writer_with_custom_handlers = Transit::Writer.new(:json, io,
309
+ # :handlers => {Point => PointWriteHandler})
310
+ #
311
+ # @see Transit::WriteHandlers
312
+ def initialize(format, io, opts={})
313
+ @marshaler = case format
314
+ when :json
315
+ require 'oj'
316
+ JsonMarshaler.new(io,
317
+ {:prefer_strings => true,
318
+ :verbose => false,
319
+ :handlers => {}}.merge(opts))
320
+ when :json_verbose
321
+ require 'oj'
322
+ VerboseJsonMarshaler.new(io,
323
+ {:prefer_strings => true,
324
+ :verbose => true,
325
+ :handlers => {}}.merge(opts))
326
+ else
327
+ require 'msgpack'
328
+ MessagePackMarshaler.new(io,
329
+ {:prefer_strings => false,
330
+ :verbose => false,
331
+ :handlers => {}}.merge(opts))
332
+ end
333
+ end
334
+
335
+ # Converts a Ruby object to a transit value and writes it to this
336
+ # Writer's output stream.
337
+ #
338
+ # @param obj the value to write
339
+ # @example
340
+ # writer = Transit::Writer.new(:json, io)
341
+ # writer.write(Date.new(2014,7,22))
342
+ def write(obj)
343
+ @marshaler.marshal_top(obj)
344
+ end
345
+ end
346
+ end