transit-ruby 0.8.467

Sign up to get free protection for your applications and to get access to all the features.
@@ -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