transit-ruby 0.8.467
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +1 -0
- data.tar.gz.sig +1 -0
- data/.yard_redcarpet_ext +1 -0
- data/.yardopts +5 -0
- data/LICENSE +202 -0
- data/README.md +167 -0
- data/lib/transit.rb +86 -0
- data/lib/transit/date_time_util.rb +39 -0
- data/lib/transit/decoder.rb +115 -0
- data/lib/transit/read_handlers.rb +98 -0
- data/lib/transit/reader.rb +117 -0
- data/lib/transit/rolling_cache.rb +70 -0
- data/lib/transit/transit_types.rb +251 -0
- data/lib/transit/write_handlers.rb +406 -0
- data/lib/transit/writer.rb +346 -0
- data/spec/spec_helper.rb +85 -0
- data/spec/transit/date_time_util_spec.rb +65 -0
- data/spec/transit/decoder_spec.rb +60 -0
- data/spec/transit/exemplar_spec.rb +149 -0
- data/spec/transit/reader_spec.rb +129 -0
- data/spec/transit/rolling_cache_spec.rb +94 -0
- data/spec/transit/round_trip_spec.rb +179 -0
- data/spec/transit/transit_types_spec.rb +136 -0
- data/spec/transit/writer_spec.rb +277 -0
- metadata +271 -0
- metadata.gz.sig +1 -0
@@ -0,0 +1,39 @@
|
|
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
|
+
# @api private
|
17
|
+
module DateTimeUtil
|
18
|
+
def to_millis(v)
|
19
|
+
case v
|
20
|
+
when DateTime
|
21
|
+
t = v.new_offset(0).to_time
|
22
|
+
when Date
|
23
|
+
t = Time.gm(v.year, v.month, v.day)
|
24
|
+
when Time
|
25
|
+
t = v.getutc
|
26
|
+
else
|
27
|
+
raise "Don't know how to get millis from #{t.inspect}"
|
28
|
+
end
|
29
|
+
(t.to_i * 1000) + (t.usec / 1000)
|
30
|
+
end
|
31
|
+
|
32
|
+
def from_millis(millis)
|
33
|
+
t = Time.at(millis / 1000).utc
|
34
|
+
DateTime.new(t.year, t.month, t.day, t.hour, t.min, t.sec + (millis % 1000 * 0.001))
|
35
|
+
end
|
36
|
+
|
37
|
+
module_function :to_millis, :from_millis
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,115 @@
|
|
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
|
+
# Converts a transit value to an instance of a type
|
17
|
+
# @api private
|
18
|
+
class Decoder
|
19
|
+
ESC_ESC = "#{ESC}#{ESC}"
|
20
|
+
ESC_SUB = "#{ESC}#{SUB}"
|
21
|
+
ESC_RES = "#{ESC}#{RES}"
|
22
|
+
|
23
|
+
IDENTITY = ->(v){v}
|
24
|
+
|
25
|
+
GROUND_TAGS = %w[_ s ? i d b ' array map]
|
26
|
+
|
27
|
+
def initialize(options={})
|
28
|
+
custom_handlers = options[:handlers] || {}
|
29
|
+
custom_handlers.each {|k,v| validate_handler(k,v)}
|
30
|
+
@handlers = ReadHandlers::DEFAULT_READ_HANDLERS.merge(custom_handlers)
|
31
|
+
@default_handler = options[:default_handler] || ReadHandlers::DEFAULT_READ_HANDLER
|
32
|
+
end
|
33
|
+
|
34
|
+
# @api private
|
35
|
+
class Tag < String; end
|
36
|
+
|
37
|
+
# Decodes a transit value to a corresponding object
|
38
|
+
#
|
39
|
+
# @param node a transit value to be decoded
|
40
|
+
# @param cache
|
41
|
+
# @param as_map_key
|
42
|
+
# @return decoded object
|
43
|
+
def decode(node, cache=RollingCache.new, as_map_key=false)
|
44
|
+
case node
|
45
|
+
when String
|
46
|
+
if cache.has_key?(node)
|
47
|
+
cache.read(node)
|
48
|
+
else
|
49
|
+
parsed = begin
|
50
|
+
if !node.start_with?(ESC)
|
51
|
+
node
|
52
|
+
elsif node.start_with?(TAG)
|
53
|
+
Tag.new(node[2..-1])
|
54
|
+
elsif handler = @handlers[node[1]]
|
55
|
+
handler.from_rep(node[2..-1])
|
56
|
+
elsif node.start_with?(ESC_ESC, ESC_SUB, ESC_RES)
|
57
|
+
node[1..-1]
|
58
|
+
else
|
59
|
+
@default_handler.from_rep(node[1], node[2..-1])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
if cache.cacheable?(node, as_map_key)
|
63
|
+
cache.write(parsed)
|
64
|
+
end
|
65
|
+
parsed
|
66
|
+
end
|
67
|
+
when Array
|
68
|
+
return node if node.empty?
|
69
|
+
e0 = decode(node.shift, cache, false)
|
70
|
+
if e0 == MAP_AS_ARRAY
|
71
|
+
decode(Hash[*node], cache)
|
72
|
+
elsif Tag === e0
|
73
|
+
v = decode(node.shift, cache)
|
74
|
+
if handler = @handlers[e0]
|
75
|
+
handler.from_rep(v)
|
76
|
+
else
|
77
|
+
@default_handler.from_rep(e0,v)
|
78
|
+
end
|
79
|
+
else
|
80
|
+
[e0] + node.map {|e| decode(e, cache, as_map_key)}
|
81
|
+
end
|
82
|
+
when Hash
|
83
|
+
if node.size == 1
|
84
|
+
k = decode(node.keys.first, cache, true)
|
85
|
+
v = decode(node.values.first, cache, false)
|
86
|
+
if Tag === k
|
87
|
+
if handler = @handlers[k]
|
88
|
+
handler.from_rep(v)
|
89
|
+
else
|
90
|
+
@default_handler.from_rep(k,v)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
{k => v}
|
94
|
+
end
|
95
|
+
else
|
96
|
+
node.keys.each do |k|
|
97
|
+
node.store(decode(k, cache, true), decode(node.delete(k), cache))
|
98
|
+
end
|
99
|
+
node
|
100
|
+
end
|
101
|
+
else
|
102
|
+
node
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def validate_handler(key, handler)
|
107
|
+
raise ArgumentError.new(CAN_NOT_OVERRIDE_GROUND_TYPES_MESSAGE) if GROUND_TAGS.include?(key)
|
108
|
+
end
|
109
|
+
|
110
|
+
CAN_NOT_OVERRIDE_GROUND_TYPES_MESSAGE = <<-MSG
|
111
|
+
You can not supply custom read handlers for ground types.
|
112
|
+
MSG
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,98 @@
|
|
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
|
+
# @see Transit::WriteHandlers
|
17
|
+
module ReadHandlers
|
18
|
+
class Default
|
19
|
+
def from_rep(tag,val) TaggedValue.new(tag, val) end
|
20
|
+
end
|
21
|
+
class NilHandler
|
22
|
+
def from_rep(_) nil end
|
23
|
+
end
|
24
|
+
class KeywordHandler
|
25
|
+
def from_rep(v) v.to_sym end
|
26
|
+
end
|
27
|
+
class BooleanHandler
|
28
|
+
def from_rep(v) v == "t" end
|
29
|
+
end
|
30
|
+
class ByteArrayHandler
|
31
|
+
def from_rep(v) ByteArray.from_base64(v) end
|
32
|
+
end
|
33
|
+
class FloatHandler
|
34
|
+
def from_rep(v) Float(v) end
|
35
|
+
end
|
36
|
+
class IntegerHandler
|
37
|
+
def from_rep(v) v.to_i end
|
38
|
+
end
|
39
|
+
class BigIntegerHandler
|
40
|
+
def from_rep(v) v.to_i end
|
41
|
+
end
|
42
|
+
class BigDecimalHandler
|
43
|
+
def from_rep(v) BigDecimal.new(v) end
|
44
|
+
end
|
45
|
+
class IdentityHandler
|
46
|
+
def from_rep(v) v end
|
47
|
+
end
|
48
|
+
class SymbolHandler
|
49
|
+
def from_rep(v) Transit::Symbol.new(v) end
|
50
|
+
end
|
51
|
+
class TimeStringHandler
|
52
|
+
def from_rep(v) DateTime.iso8601(v) end
|
53
|
+
end
|
54
|
+
class TimeIntHandler
|
55
|
+
def from_rep(v) DateTimeUtil.from_millis(v.to_i) end
|
56
|
+
end
|
57
|
+
class UuidHandler
|
58
|
+
def from_rep(v) UUID.new(v) end
|
59
|
+
end
|
60
|
+
class UriHandler
|
61
|
+
def from_rep(v) Addressable::URI.parse(v) end
|
62
|
+
end
|
63
|
+
class SetHandler
|
64
|
+
def from_rep(v) Set.new(v) end
|
65
|
+
end
|
66
|
+
class LinkHandler
|
67
|
+
def from_rep(v) Link.new(v) end
|
68
|
+
end
|
69
|
+
class CmapHandler
|
70
|
+
def from_rep(v) Hash[*v] end
|
71
|
+
end
|
72
|
+
|
73
|
+
DEFAULT_READ_HANDLERS = {
|
74
|
+
"_" => NilHandler.new,
|
75
|
+
":" => KeywordHandler.new,
|
76
|
+
"?" => BooleanHandler.new,
|
77
|
+
"b" => ByteArrayHandler.new,
|
78
|
+
"d" => FloatHandler.new,
|
79
|
+
"i" => IntegerHandler.new,
|
80
|
+
"n" => BigIntegerHandler.new,
|
81
|
+
"f" => BigDecimalHandler.new,
|
82
|
+
"c" => IdentityHandler.new,
|
83
|
+
"$" => SymbolHandler.new,
|
84
|
+
"t" => TimeStringHandler.new,
|
85
|
+
"m" => TimeIntHandler.new,
|
86
|
+
"u" => UuidHandler.new,
|
87
|
+
"r" => UriHandler.new,
|
88
|
+
"'" => IdentityHandler.new,
|
89
|
+
"set" => SetHandler.new,
|
90
|
+
"link" => LinkHandler.new,
|
91
|
+
"list" => IdentityHandler.new,
|
92
|
+
"cmap" => CmapHandler.new
|
93
|
+
}.freeze
|
94
|
+
|
95
|
+
DEFAULT_READ_HANDLER = Default.new
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,117 @@
|
|
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::Reader converts incoming transit data into appropriate
|
17
|
+
# values/objects in Ruby.
|
18
|
+
# @see https://github.com/cognitect/transit-format
|
19
|
+
class Reader
|
20
|
+
# @api private
|
21
|
+
class JsonUnmarshaler
|
22
|
+
class ParseHandler
|
23
|
+
def each(&block) @yield_v = block end
|
24
|
+
def add_value(v) @yield_v[v] if @yield_v end
|
25
|
+
|
26
|
+
def hash_start() {} end
|
27
|
+
def hash_set(h,k,v) h.store(k,v) end
|
28
|
+
def array_start() [] end
|
29
|
+
def array_append(a,v) a << v end
|
30
|
+
|
31
|
+
def error(message, line, column)
|
32
|
+
raise Exception.new(message, line, column)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(io, opts)
|
37
|
+
@io = io
|
38
|
+
@decoder = Transit::Decoder.new(opts)
|
39
|
+
@parse_handler = ParseHandler.new
|
40
|
+
end
|
41
|
+
|
42
|
+
# @see Reader#read
|
43
|
+
def read
|
44
|
+
if block_given?
|
45
|
+
@parse_handler.each {|v| yield @decoder.decode(v)}
|
46
|
+
else
|
47
|
+
@parse_handler.each {|v| return @decoder.decode(v)}
|
48
|
+
end
|
49
|
+
Oj.sc_parse(@parse_handler, @io)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
class MessagePackUnmarshaler
|
55
|
+
def initialize(io, opts)
|
56
|
+
@decoder = Transit::Decoder.new(opts)
|
57
|
+
@unpacker = MessagePack::Unpacker.new(io)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @see Reader#read
|
61
|
+
def read
|
62
|
+
if block_given?
|
63
|
+
@unpacker.each {|v| yield @decoder.decode(v)}
|
64
|
+
else
|
65
|
+
@decoder.decode(@unpacker.read)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
extend Forwardable
|
71
|
+
|
72
|
+
# @!method read
|
73
|
+
# Reads transit values from an IO (file, stream, etc), and
|
74
|
+
# converts each one to the appropriate Ruby object.
|
75
|
+
#
|
76
|
+
# With a block, yields each object to the block as it is processed.
|
77
|
+
#
|
78
|
+
# Without a block, returns a single object.
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# reader = Transit::Reader.new(:json, io)
|
82
|
+
# reader.read {|obj| do_something_with(obj)}
|
83
|
+
#
|
84
|
+
# reader = Transit::Reader.new(:json, io)
|
85
|
+
# obj = reader.read
|
86
|
+
def_delegators :@reader, :read
|
87
|
+
|
88
|
+
# @param [Symbol] format required any of :msgpack, :json, :json_verbose
|
89
|
+
# @param [IO] io required
|
90
|
+
# @param [Hash] opts optional
|
91
|
+
# Creates a new Reader configured to read from <tt>io</tt>,
|
92
|
+
# expecting <tt>format</tt> (<tt>:json</tt>, <tt>:msgpack</tt>).
|
93
|
+
#
|
94
|
+
# Use opts to register custom read handlers, associating each one
|
95
|
+
# with its tag.
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
#
|
99
|
+
# json_reader = Transit::Reader.new(:json, io)
|
100
|
+
# # ^^ reads both :json and :json_verbose formats ^^
|
101
|
+
# msgpack_writer = Transit::Reader.new(:msgpack, io)
|
102
|
+
# writer_with_custom_handlers = Transit::Reader.new(:json, io,
|
103
|
+
# :handlers => {"point" => PointReadHandler})
|
104
|
+
#
|
105
|
+
# @see Transit::ReadHandlers
|
106
|
+
def initialize(format, io, opts={})
|
107
|
+
@reader = case format
|
108
|
+
when :json, :json_verbose
|
109
|
+
require 'oj'
|
110
|
+
JsonUnmarshaler.new(io, opts)
|
111
|
+
else
|
112
|
+
require 'msgpack'
|
113
|
+
MessagePackUnmarshaler.new(io, opts)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,70 @@
|
|
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
|
+
# @api private
|
17
|
+
class RollingCache
|
18
|
+
extend Forwardable
|
19
|
+
|
20
|
+
def_delegators "@key_to_value", :has_key?, :size
|
21
|
+
|
22
|
+
FIRST_ORD = 48
|
23
|
+
LAST_ORD = 91
|
24
|
+
CACHE_CODE_DIGITS = 44;
|
25
|
+
CACHE_SIZE = CACHE_CODE_DIGITS * CACHE_CODE_DIGITS;
|
26
|
+
MIN_SIZE_CACHEABLE = 4
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
clear
|
30
|
+
end
|
31
|
+
|
32
|
+
def read(key)
|
33
|
+
@key_to_value[key]
|
34
|
+
end
|
35
|
+
|
36
|
+
def write(val)
|
37
|
+
@value_to_key[val] || begin
|
38
|
+
clear if @key_to_value.size >= CACHE_SIZE
|
39
|
+
key = next_key(@key_to_value.size)
|
40
|
+
@value_to_key[val] = key
|
41
|
+
@key_to_value[key] = val
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def cache_key?(str, _=false)
|
46
|
+
str[0] == SUB && str != MAP_AS_ARRAY
|
47
|
+
end
|
48
|
+
|
49
|
+
def cacheable?(str, as_map_key=false)
|
50
|
+
str.size >= MIN_SIZE_CACHEABLE && (as_map_key || str.start_with?("~#","~$","~:"))
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def clear
|
56
|
+
@key_to_value = {}
|
57
|
+
@value_to_key = {}
|
58
|
+
end
|
59
|
+
|
60
|
+
def next_key(i)
|
61
|
+
hi = i / CACHE_CODE_DIGITS;
|
62
|
+
lo = i % CACHE_CODE_DIGITS;
|
63
|
+
if hi == 0
|
64
|
+
"^#{(lo+FIRST_ORD).chr}"
|
65
|
+
else
|
66
|
+
"^#{(hi+FIRST_ORD).chr}#{(lo+FIRST_ORD).chr}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,251 @@
|
|
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
|
+
class Wrapper
|
17
|
+
extend Forwardable
|
18
|
+
|
19
|
+
def_delegators :@value, :hash, :to_sym, :to_s
|
20
|
+
|
21
|
+
attr_reader :value
|
22
|
+
|
23
|
+
def initialize(value)
|
24
|
+
@value = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
other.is_a?(self.class) && @value == other.value
|
29
|
+
end
|
30
|
+
alias eql? ==
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
"<#{self.class} \"#{to_s}\">"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Represents a transit symbol extension type.
|
38
|
+
# @see https://github.com/cognitect/transit-format
|
39
|
+
class Symbol < Wrapper
|
40
|
+
def initialize(sym)
|
41
|
+
super sym.to_sym
|
42
|
+
end
|
43
|
+
|
44
|
+
def namespace
|
45
|
+
@namespace ||= parsed[-2]
|
46
|
+
end
|
47
|
+
|
48
|
+
def name
|
49
|
+
@name ||= parsed[-1] || "/"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def parsed
|
55
|
+
@parsed ||= @value.to_s.split("/")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Represents a transit byte array extension type.
|
60
|
+
# @see https://github.com/cognitect/transit-format
|
61
|
+
class ByteArray < Wrapper
|
62
|
+
def self.from_base64(data)
|
63
|
+
new(Base64.decode64(data))
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_base64
|
67
|
+
Base64.encode64(@value)
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
@value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Represents a transit UUID extension type.
|
76
|
+
# @see https://github.com/cognitect/transit-format
|
77
|
+
class UUID
|
78
|
+
def self.random
|
79
|
+
new
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(uuid_or_most_significant_bits=nil,least_significant_bits=nil)
|
83
|
+
case uuid_or_most_significant_bits
|
84
|
+
when String
|
85
|
+
@string_rep = uuid_or_most_significant_bits
|
86
|
+
when Array
|
87
|
+
@numeric_rep = uuid_or_most_significant_bits.map {|n| twos_complement(n)}
|
88
|
+
when Numeric
|
89
|
+
@numeric_rep = [twos_complement(uuid_or_most_significant_bits), twos_complement(least_significant_bits)]
|
90
|
+
when nil
|
91
|
+
@string_rep = SecureRandom.uuid
|
92
|
+
else
|
93
|
+
raise "Can't build UUID from #{uuid_or_most_significant_bits.inspect}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_s
|
98
|
+
@string_rep ||= numbers_to_string
|
99
|
+
end
|
100
|
+
|
101
|
+
def most_significant_bits
|
102
|
+
@most_significant_bits ||= numeric_rep[0]
|
103
|
+
end
|
104
|
+
|
105
|
+
def least_significant_bits
|
106
|
+
@least_significant_bits ||= numeric_rep[1]
|
107
|
+
end
|
108
|
+
|
109
|
+
def inspect
|
110
|
+
@inspect ||= "<#{self.class} \"#{to_s}\">"
|
111
|
+
end
|
112
|
+
|
113
|
+
def ==(other)
|
114
|
+
return false unless other.is_a?(self.class)
|
115
|
+
if @numeric_rep
|
116
|
+
other.most_significant_bits == most_significant_bits &&
|
117
|
+
other.least_significant_bits == least_significant_bits
|
118
|
+
else
|
119
|
+
other.to_s == @string_rep
|
120
|
+
end
|
121
|
+
end
|
122
|
+
alias eql? ==
|
123
|
+
|
124
|
+
def hash
|
125
|
+
most_significant_bits.hash + least_significant_bits.hash
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def numeric_rep
|
131
|
+
@numeric_rep ||= string_to_numbers
|
132
|
+
end
|
133
|
+
|
134
|
+
def numbers_to_string
|
135
|
+
most_significant_bits = @numeric_rep[0]
|
136
|
+
least_significant_bits = @numeric_rep[1]
|
137
|
+
digits(most_significant_bits >> 32, 8) + "-" +
|
138
|
+
digits(most_significant_bits >> 16, 4) + "-" +
|
139
|
+
digits(most_significant_bits, 4) + "-" +
|
140
|
+
digits(least_significant_bits >> 48, 4) + "-" +
|
141
|
+
digits(least_significant_bits, 12)
|
142
|
+
end
|
143
|
+
|
144
|
+
def string_to_numbers
|
145
|
+
str = @string_rep.delete("-")
|
146
|
+
[twos_complement(str[ 0..15].hex), twos_complement(str[16..31].hex)]
|
147
|
+
end
|
148
|
+
|
149
|
+
def digits(val, digits)
|
150
|
+
hi = 1 << (digits*4)
|
151
|
+
(hi | (val & (hi - 1))).to_s(16)[1..-1]
|
152
|
+
end
|
153
|
+
|
154
|
+
def twos_complement(integer_value, num_of_bits=64)
|
155
|
+
max_signed = 2**(num_of_bits-1)
|
156
|
+
max_unsigned = 2**num_of_bits
|
157
|
+
(integer_value >= max_signed) ? integer_value - max_unsigned : integer_value
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Represents a transit hypermedia link extension type.
|
162
|
+
# @see https://github.com/cognitect/transit-format
|
163
|
+
# @see http://amundsen.com/media-types/collection/format/#arrays-links
|
164
|
+
class Link
|
165
|
+
KEYS = ["href", "rel", "name", "render", "prompt"]
|
166
|
+
RENDER_VALUES = ["link", "image"]
|
167
|
+
|
168
|
+
# @overload Link.new(hash)
|
169
|
+
# @param [Hash] hash
|
170
|
+
# Valid keys are:
|
171
|
+
# "href" required, String or Addressable::URI
|
172
|
+
# "rel" required, String
|
173
|
+
# "name" optional, String
|
174
|
+
# "render" optional, String (only "link" or "image")
|
175
|
+
# "prompt" optional, String
|
176
|
+
# @overload Link.new(href, rel, name, render, prompt)
|
177
|
+
# @param [String, Addressable::URI] href required
|
178
|
+
# @param [String] rel required
|
179
|
+
# @param [String] name optional
|
180
|
+
# @param [String] render optional (only "link" or "image")
|
181
|
+
# @param [String] prompt optional
|
182
|
+
def initialize(*args)
|
183
|
+
@values = if args[0].is_a?(Hash)
|
184
|
+
reconcile_values(args[0])
|
185
|
+
elsif args.length >= 2 && (args[0].is_a?(Addressable::URI) || args[0].is_a?(String))
|
186
|
+
reconcile_values(Hash[KEYS.zip(args)])
|
187
|
+
else
|
188
|
+
raise ArgumentError, "The first argument to Link.new can be a URI, String or a Hash. When the first argument is a URI or String, the second argument, rel, must present."
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def href; @href ||= @values["href"] end
|
193
|
+
def rel; @rel ||= @values["rel"] end
|
194
|
+
def name; @name ||= @values["name"] end
|
195
|
+
def render; @render ||= @values["render"] end
|
196
|
+
def prompt; @prompt ||= @values["prompt"] end
|
197
|
+
|
198
|
+
def to_h
|
199
|
+
@values
|
200
|
+
end
|
201
|
+
|
202
|
+
def ==(other)
|
203
|
+
other.is_a?(Link) && other.to_h == to_h
|
204
|
+
end
|
205
|
+
alias eql? ==
|
206
|
+
|
207
|
+
def hash
|
208
|
+
@values.hash
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
def reconcile_values(map)
|
214
|
+
map.dup.tap do |m|
|
215
|
+
m["href"] = Addressable::URI.parse(m["href"]) if m["href"].is_a?(String)
|
216
|
+
if m["render"]
|
217
|
+
render = m["render"].downcase
|
218
|
+
if RENDER_VALUES.include?(render)
|
219
|
+
m["render"] = render
|
220
|
+
else
|
221
|
+
raise ArgumentError, "render must be either #{RENDER_VALUES[0]} or #{RENDER_VALUES[1]}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end.freeze
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Represents a transit tag and value. Returned by default when a
|
229
|
+
# reader encounters a tag for which there is no registered
|
230
|
+
# handler. Can also be used in a custom write handler to force
|
231
|
+
# representation to use a transit ground type using a rep for which
|
232
|
+
# there is no registered handler (e.g., an iterable for the
|
233
|
+
# representation of an array).
|
234
|
+
# @see https://github.com/cognitect/transit-format
|
235
|
+
class TaggedValue
|
236
|
+
attr_reader :tag, :rep
|
237
|
+
def initialize(tag, rep)
|
238
|
+
@tag = tag
|
239
|
+
@rep = rep
|
240
|
+
end
|
241
|
+
|
242
|
+
def ==(other)
|
243
|
+
other.is_a?(self.class) && other.tag == @tag && other.rep == @rep
|
244
|
+
end
|
245
|
+
alias eql? ==
|
246
|
+
|
247
|
+
def hash
|
248
|
+
@tag.hash + @rep.hash
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|