slow_blink 0.0.4 → 0.0.5
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.
- checksums.yaml +4 -4
- data/ext/slow_blink/ext_schema_parser/lexer.c +168 -177
- data/ext/slow_blink/ext_schema_parser/lexer.h +1 -1
- data/ext/slow_blink/ext_schema_parser/parser.c +303 -306
- data/ext/slow_blink/ext_schema_parser/parser.h +11 -12
- data/ext/slow_blink/{ext_compact_encoder → message/ext_compact_encoder}/ext_compact_encoder.c +3 -1
- data/ext/slow_blink/message/ext_compact_encoder/extconf.rb +4 -0
- data/lib/slow_blink/error.rb +1 -1
- data/lib/slow_blink/message/binary.rb +19 -26
- data/lib/slow_blink/message/boolean.rb +17 -17
- data/lib/slow_blink/message/date.rb +24 -0
- data/lib/slow_blink/message/decimal.rb +34 -0
- data/lib/slow_blink/message/enumeration.rb +24 -23
- data/lib/slow_blink/message/field.rb +45 -6
- data/lib/slow_blink/message/fixed.rb +35 -25
- data/lib/slow_blink/message/floating_point.rb +16 -17
- data/lib/slow_blink/message/group.rb +242 -136
- data/lib/slow_blink/message/integer.rb +77 -51
- data/lib/slow_blink/message/model.rb +121 -85
- data/lib/slow_blink/message/sequence.rb +43 -28
- data/lib/slow_blink/message/string.rb +17 -31
- data/lib/slow_blink/message/time.rb +55 -17
- data/lib/slow_blink/message/time_of_day.rb +40 -50
- data/lib/slow_blink/schema.rb +17 -6
- data/lib/slow_blink/version.rb +1 -1
- data/rakefile +12 -7
- data/test/tc_incr_annote.rb +16 -0
- data/test/tc_inputs.rb +2 -2
- data/test/tc_model_dynamic_group.rb +105 -0
- data/test/tc_model_extension.rb +143 -0
- data/test/tc_model_static_group.rb +27 -36
- data/test/tc_model_think_blink.rb +18 -35
- metadata +12 -9
- data/ext/slow_blink/ext_compact_encoder/extconf.rb +0 -4
- /data/ext/slow_blink/{ext_compact_encoder → message/ext_compact_encoder}/compact_encoder.c +0 -0
- /data/ext/slow_blink/{ext_compact_encoder → message/ext_compact_encoder}/compact_encoder.h +0 -0
@@ -19,99 +19,113 @@
|
|
19
19
|
|
20
20
|
module SlowBlink
|
21
21
|
|
22
|
+
# This module is concerned with generating models from Schema that are optimised for encoding/decoding and enforcing constraints
|
22
23
|
module Message
|
23
24
|
|
24
|
-
class Error <
|
25
|
+
class Error < StandardError
|
25
26
|
end
|
26
27
|
|
27
28
|
class Model
|
28
29
|
|
30
|
+
# the maximum level of nesting in messages able to be decoded by models
|
31
|
+
DEFAULT_MAX_RECURSION = 100
|
32
|
+
|
29
33
|
# @api user
|
30
34
|
#
|
31
35
|
# Create a Model from a {Schema}
|
32
36
|
#
|
33
37
|
# @param schema [SlowBlink::Schema]
|
34
|
-
|
38
|
+
# @param opts [Hash]
|
39
|
+
#
|
40
|
+
# @option opts [Symbol] :maxRecursion
|
41
|
+
#
|
42
|
+
def initialize(schema, **opts)
|
43
|
+
|
35
44
|
@schema = schema
|
36
|
-
@taggedGroups = {}
|
37
|
-
|
38
|
-
|
39
|
-
|
45
|
+
@taggedGroups = {}
|
46
|
+
@groups = {}
|
47
|
+
@maxRecursion = opts[:maxRecursion]||DEFAULT_MAX_RECURSION
|
48
|
+
maxRecursion = @maxRecursion
|
49
|
+
|
50
|
+
# define the extension object
|
51
|
+
groups = @groups
|
40
52
|
taggedGroups = @taggedGroups
|
41
|
-
|
53
|
+
permitted = schema.tagged.keys
|
54
|
+
@extensionObject = Class.new(DynamicGroup) do
|
55
|
+
@maxRecursion = maxRecursion
|
56
|
+
@extensionObject = self
|
42
57
|
@opt = false
|
43
|
-
@groups =
|
44
|
-
@
|
45
|
-
|
58
|
+
@groups = groups
|
59
|
+
@taggedGroups = taggedGroups
|
60
|
+
@permitted = permitted
|
61
|
+
end
|
62
|
+
extensionObject = @extensionObject
|
63
|
+
|
64
|
+
schema.groups.each do |name, g|
|
65
|
+
this = self
|
66
|
+
@groups[name] = Class.new(Group) do
|
67
|
+
@extensionObject = extensionObject
|
68
|
+
@maxRecursion = maxRecursion
|
69
|
+
@name = g.nameWithID.name
|
70
|
+
@id = g.nameWithID.id
|
71
|
+
@fields = {}
|
72
|
+
g.fields.each do |f|
|
73
|
+
@fields[f.nameWithID.name] = this._model_field(f)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
if g.nameWithID.id
|
77
|
+
@taggedGroups[g.nameWithID.id] = @groups[name]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
46
82
|
end
|
47
83
|
|
48
84
|
# @api user
|
49
85
|
#
|
50
86
|
# Initialise a message model instance with a compact form string
|
51
87
|
#
|
52
|
-
# @note return value will be an *anonymous* *subclass* *instance* of {
|
88
|
+
# @note return value will be an *anonymous* *subclass* *instance* of {Group}
|
53
89
|
#
|
54
|
-
# @param [String] Blink Protocol compact form
|
55
|
-
# @return [
|
90
|
+
# @param input [String] Blink Protocol compact form
|
91
|
+
# @return [Group] group instance
|
56
92
|
#
|
57
93
|
def decode_compact(input)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
# @param name [String] name of group
|
76
|
-
# @yield [Group] group instance to initalise
|
77
|
-
# @return [StaticGroup,DynamicGroup] group instance
|
78
|
-
#
|
79
|
-
def group(name, data=nil, &block)
|
80
|
-
group = @top.groups.values.detect{|g|g.name == name}
|
81
|
-
if group
|
82
|
-
top = @top.new(group.new(data))
|
83
|
-
if block
|
84
|
-
self.instance_exec(top, &block)
|
85
|
-
# validate optional constraint here
|
94
|
+
stack = []
|
95
|
+
inputSize = input.size
|
96
|
+
buf = input.getBinary!
|
97
|
+
if buf.size > 0
|
98
|
+
id = buf.getU64!
|
99
|
+
groupClass = @taggedGroups[id]
|
100
|
+
begin
|
101
|
+
if groupClass
|
102
|
+
group = groupClass.from_compact!(buf, stack)
|
103
|
+
else
|
104
|
+
raise Error.new "W2: Group id #{group.id} is unknown"
|
105
|
+
end
|
106
|
+
rescue Error => ex
|
107
|
+
puts ex
|
108
|
+
puts "encountered at offset #{inputSize - input.size}"
|
109
|
+
puts stack.last.name
|
110
|
+
raise
|
86
111
|
end
|
87
|
-
top
|
88
112
|
else
|
89
|
-
raise
|
113
|
+
raise Error.new "W1: Top level group cannot be null "
|
90
114
|
end
|
91
115
|
end
|
92
116
|
|
93
|
-
# @api
|
117
|
+
# @api user
|
94
118
|
#
|
95
|
-
#
|
119
|
+
# Get a group model
|
96
120
|
#
|
97
|
-
# @param
|
98
|
-
# @
|
99
|
-
# @return [
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
@implements = group.class
|
104
|
-
@name = group.nameWithID.name
|
105
|
-
@id = group.nameWithID.id
|
106
|
-
@opt = opt
|
107
|
-
@fields = group.fields.inject([]) do |fields, f|
|
108
|
-
fields << this._model_field(f)
|
109
|
-
end
|
110
|
-
|
111
|
-
end
|
121
|
+
# @param name [String] name of group (may be qualified)
|
122
|
+
# @return [Class] {DynamicGroup} or {Group}
|
123
|
+
# @return [nil] group not defined
|
124
|
+
#
|
125
|
+
def group(name)
|
126
|
+
@groups[name]
|
112
127
|
end
|
113
128
|
|
114
|
-
|
115
129
|
# @api private
|
116
130
|
#
|
117
131
|
# Create a model for a Field
|
@@ -121,11 +135,10 @@ module SlowBlink
|
|
121
135
|
def _model_field(field)
|
122
136
|
this = self
|
123
137
|
Class.new(Field) do
|
124
|
-
@implements = field.class
|
125
138
|
@opt = field.opt?
|
126
139
|
@name = field.nameWithID.name
|
127
140
|
@id = field.nameWithID.id
|
128
|
-
@type = this._model_type(field)
|
141
|
+
@type = this._model_type(field.type, field.opt?)
|
129
142
|
end
|
130
143
|
end
|
131
144
|
|
@@ -133,50 +146,71 @@ module SlowBlink
|
|
133
146
|
#
|
134
147
|
# Create a model for a type
|
135
148
|
#
|
136
|
-
# @param
|
137
|
-
# @
|
138
|
-
|
139
|
-
|
140
|
-
|
149
|
+
# @param type [SlowBlink::Type] type definition
|
150
|
+
# @param opt [true,false] parent definition may allow this type to be optional
|
151
|
+
#
|
152
|
+
#
|
153
|
+
def _model_type(type, opt)
|
154
|
+
this = self
|
155
|
+
extensionObject = @extensionObject
|
156
|
+
maxRecursion = @maxRecursion
|
141
157
|
case type.class
|
142
158
|
when SlowBlink::OBJECT
|
143
159
|
groups = @groups
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
@
|
160
|
+
taggedGroups = @taggedGroups
|
161
|
+
permitted = @taggedGroups.keys
|
162
|
+
Class.new(DynamicGroup) do
|
163
|
+
@extensionObject = extensionObject
|
164
|
+
@maxRecursion = maxRecursion
|
165
|
+
@opt = opt
|
148
166
|
@groups = groups
|
167
|
+
@taggedGroups = taggedGroups
|
149
168
|
@permitted = permitted
|
150
|
-
end
|
169
|
+
end
|
151
170
|
when SlowBlink::REF
|
152
|
-
if type.ref.kind_of? Group
|
171
|
+
if type.ref.kind_of? SlowBlink::Group
|
153
172
|
if type.dynamic?
|
173
|
+
taggedGroups = @taggedGroups
|
154
174
|
groups = @groups
|
155
|
-
permitted = @
|
175
|
+
permitted = @taggedGroups.keys
|
156
176
|
@schema.tagged.each do |id, g|
|
157
177
|
if g.group_kind_of?(type)
|
158
178
|
permitted << id
|
159
179
|
end
|
160
180
|
end
|
161
181
|
Class.new(DynamicGroup) do
|
162
|
-
@
|
163
|
-
@
|
164
|
-
@opt =
|
182
|
+
@extensionObject = extensionObject
|
183
|
+
@maxRecursion = maxRecursion
|
184
|
+
@opt = opt
|
185
|
+
@taggedGroups = taggedGroups
|
165
186
|
@groups = groups
|
166
187
|
@permitted = permitted
|
167
188
|
end
|
168
189
|
else
|
169
|
-
|
190
|
+
Class.new(StaticGroup) do
|
191
|
+
@extensionObject = extensionObject
|
192
|
+
@maxRecursion = maxRecursion
|
193
|
+
@name = type.ref.nameWithID.name
|
194
|
+
@id = nil
|
195
|
+
@opt = opt
|
196
|
+
@fields = {}
|
197
|
+
type.ref.fields.each do |f|
|
198
|
+
@fields[f.nameWithID.name] = this._model_field(f)
|
199
|
+
end
|
200
|
+
end
|
170
201
|
end
|
171
202
|
else
|
172
|
-
_model_type(
|
203
|
+
_model_type(type.ref)
|
173
204
|
end
|
174
|
-
|
205
|
+
when SlowBlink::SEQUENCE
|
206
|
+
Class.new(SEQUENCE) do
|
207
|
+
@maxRecursion = maxRecursion
|
208
|
+
@type = this._model_type(type.type, false)
|
209
|
+
end
|
210
|
+
else
|
175
211
|
Class.new(SlowBlink::Message.const_get(type.class.name.split('::').last)) do
|
176
|
-
|
177
|
-
@
|
178
|
-
@name = name
|
179
|
-
@type = type
|
212
|
+
@opt = opt
|
213
|
+
@type = type
|
180
214
|
end
|
181
215
|
end
|
182
216
|
end
|
@@ -187,6 +221,7 @@ module SlowBlink
|
|
187
221
|
|
188
222
|
end
|
189
223
|
|
224
|
+
require "slow_blink/message/ext_compact_encoder"
|
190
225
|
require "slow_blink/message/field"
|
191
226
|
require "slow_blink/message/integer"
|
192
227
|
require "slow_blink/message/string"
|
@@ -200,4 +235,5 @@ require "slow_blink/message/group"
|
|
200
235
|
require "slow_blink/message/time"
|
201
236
|
require "slow_blink/message/time_of_day"
|
202
237
|
require "slow_blink/message/date"
|
238
|
+
require "slow_blink/message/decimal"
|
203
239
|
|
@@ -20,56 +20,71 @@
|
|
20
20
|
module SlowBlink::Message
|
21
21
|
|
22
22
|
class SEQUENCE
|
23
|
+
|
24
|
+
# @return the type that repeats in this SEQUENCE
|
25
|
+
def self.type
|
26
|
+
@type
|
27
|
+
end
|
23
28
|
|
24
|
-
def self.from_compact!(input)
|
29
|
+
def self.from_compact!(input, stack)
|
30
|
+
|
31
|
+
if stack.size < @maxRecursion
|
32
|
+
stack << self
|
33
|
+
else
|
34
|
+
raise Error.new "stack limit"
|
35
|
+
end
|
36
|
+
|
25
37
|
value = []
|
26
38
|
size = input.getU32!
|
27
39
|
if size
|
28
40
|
while out.size < size do
|
29
|
-
value << @type.from_compact!(input)
|
41
|
+
value << @type.from_compact!(input, stack)
|
30
42
|
end
|
31
43
|
self.new(value)
|
32
44
|
else
|
33
|
-
|
45
|
+
nil
|
34
46
|
end
|
35
|
-
end
|
36
47
|
|
37
|
-
|
38
|
-
|
39
|
-
if value
|
40
|
-
if value.kind_of? Array
|
41
|
-
@value = v
|
42
|
-
else
|
43
|
-
raise Error.new "bad type"
|
44
|
-
end
|
45
|
-
elsif self.class.opt?
|
46
|
-
@value = nil
|
47
|
-
else
|
48
|
-
raise Error.new "value unacceptable"
|
49
|
-
end
|
48
|
+
stack.pop
|
49
|
+
|
50
50
|
end
|
51
51
|
|
52
|
+
# @return [Array]
|
52
53
|
def get
|
53
54
|
@value
|
54
55
|
end
|
55
56
|
|
57
|
+
# Set value of a SEQUENCE
|
58
|
+
#
|
56
59
|
# @param value [Array]
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
+
# @raise [TypeError]
|
61
|
+
# @raise [RangeError]
|
62
|
+
#
|
63
|
+
def set(value)
|
64
|
+
@value = []
|
65
|
+
if value.kind_of? Array
|
66
|
+
value.each do |v|
|
67
|
+
if v.kind_of? self.class.type
|
68
|
+
@value << v
|
69
|
+
else
|
70
|
+
@value << self.class.type.new(v)
|
71
|
+
end
|
72
|
+
end
|
60
73
|
else
|
61
|
-
|
74
|
+
raise TypeError.new "expecting an array"
|
62
75
|
end
|
63
76
|
end
|
64
77
|
|
78
|
+
# @note calls {#set}(value)
|
79
|
+
def initialize(value)
|
80
|
+
set(value)
|
81
|
+
end
|
82
|
+
|
65
83
|
def to_compact(out)
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
else
|
71
|
-
out.putU32(nil)
|
72
|
-
end
|
84
|
+
out.putU32(@value.size)
|
85
|
+
@value.each do |value|
|
86
|
+
value.to_compact(out)
|
87
|
+
end
|
73
88
|
end
|
74
89
|
|
75
90
|
end
|
@@ -21,15 +21,7 @@ module SlowBlink::Message
|
|
21
21
|
|
22
22
|
class STRING
|
23
23
|
|
24
|
-
def self.
|
25
|
-
@name
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.opt?
|
29
|
-
@opt
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.from_compact!(input)
|
24
|
+
def self.from_compact!(input, stack)
|
33
25
|
value = input.getString!
|
34
26
|
if value
|
35
27
|
if !@type.size or value.size <= @type.size
|
@@ -37,52 +29,46 @@ module SlowBlink::Message
|
|
37
29
|
else
|
38
30
|
raise Error.new "W7: String value exceeds maximum size"
|
39
31
|
end
|
40
|
-
elsif @opt
|
41
|
-
self.new(nil)
|
42
32
|
else
|
43
|
-
|
44
|
-
end
|
33
|
+
value
|
34
|
+
end
|
45
35
|
end
|
46
36
|
|
37
|
+
# @return [Integer,nil] maximum size of string in bytes
|
47
38
|
def self.size
|
48
39
|
@type.size
|
49
40
|
end
|
50
41
|
|
42
|
+
# @return [String]
|
51
43
|
def get
|
52
44
|
@value
|
53
45
|
end
|
54
46
|
|
47
|
+
# Set a string value
|
48
|
+
# @param value [String]
|
49
|
+
# @raise [TypeError]
|
50
|
+
# @raise [RangeError] value is larger than {::size}
|
55
51
|
def set(value)
|
56
|
-
if value
|
57
|
-
if value.
|
58
|
-
|
59
|
-
@value = value
|
60
|
-
else
|
61
|
-
raise Error.new "string cannot be larger than #{self.class.size} bytes"
|
62
|
-
end
|
52
|
+
if value.kind_of? String
|
53
|
+
if !self.class.size or value.size <= self.class.size
|
54
|
+
@value = value.to_s
|
63
55
|
else
|
64
|
-
raise
|
56
|
+
raise RangeError.new "String instance cannot be larger than #{self.class.size} bytes"
|
65
57
|
end
|
66
|
-
elsif self.class.opt?
|
67
|
-
@value = nil
|
68
58
|
else
|
69
|
-
raise
|
70
|
-
end
|
59
|
+
raise TypeError.new "expecting a String instance"
|
60
|
+
end
|
71
61
|
end
|
72
62
|
|
63
|
+
# @note calls {#set}(value)
|
73
64
|
def initialize(value)
|
74
|
-
|
75
|
-
if value
|
76
|
-
set(value)
|
77
|
-
end
|
65
|
+
set(value)
|
78
66
|
end
|
79
67
|
|
80
68
|
def to_compact(out)
|
81
69
|
out.putString(@value)
|
82
70
|
end
|
83
71
|
|
84
|
-
|
85
|
-
|
86
72
|
end
|
87
73
|
|
88
74
|
end
|
@@ -17,46 +17,84 @@
|
|
17
17
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
18
|
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
19
19
|
|
20
|
-
|
20
|
+
require 'time'
|
21
21
|
|
22
|
+
module SlowBlink::Message
|
22
23
|
|
23
24
|
class MILLI_TIME
|
24
25
|
|
25
|
-
def self.from_compact!(input)
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
def set(value)
|
26
|
+
def self.from_compact!(input, stack)
|
27
|
+
value = input.getI64!
|
30
28
|
if value
|
31
|
-
|
32
|
-
elsif self.class.opt?
|
33
|
-
@value = nil
|
29
|
+
self.new(value)
|
34
30
|
else
|
35
|
-
|
31
|
+
value
|
36
32
|
end
|
37
33
|
end
|
38
34
|
|
35
|
+
# @return [DateTime]
|
39
36
|
def get
|
40
37
|
@value
|
41
38
|
end
|
42
39
|
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
# @overload set(value)
|
41
|
+
# Set a millisecond time value
|
42
|
+
# @param value [Time,DateTime,Date]
|
43
|
+
# @overload set(value)
|
44
|
+
# @param value [String] time in ISO 8601
|
45
|
+
# @overload set(value)
|
46
|
+
# @param value [String] time in milliseconds since UNIX epoch
|
47
|
+
# @raise [TypeError]
|
48
|
+
# @raise [ArgumentError]
|
49
|
+
def set(value)
|
50
|
+
if value.kind_of? Time or value.kind_of? DateTime or value.kind_of? Date
|
51
|
+
@value = time.to_datetime
|
52
|
+
elsif value.kind_of? String
|
53
|
+
@value = DateTime.parse(value)
|
54
|
+
elsif value.kind_of? Integer
|
55
|
+
@value = DateTime.new(value)
|
46
56
|
else
|
47
|
-
|
48
|
-
end
|
57
|
+
raise TypeError
|
58
|
+
end
|
49
59
|
end
|
50
60
|
|
51
|
-
|
52
|
-
|
61
|
+
# @note calls {#set}(value)
|
62
|
+
def initialize(value)
|
63
|
+
set(value)
|
53
64
|
end
|
54
65
|
|
66
|
+
def to_compact(out)
|
67
|
+
out.putI64(@value.strftime('%Q'))
|
68
|
+
end
|
55
69
|
|
56
70
|
end
|
57
71
|
|
58
72
|
class NANO_TIME < MILLI_TIME
|
59
73
|
|
74
|
+
def to_compact(out)
|
75
|
+
out.putI64(@value.strftime('%N'))
|
76
|
+
end
|
77
|
+
|
78
|
+
# @overload set(value)
|
79
|
+
# Set a nanosecond resolution time value
|
80
|
+
# @param value [Time,DateTime,Date]
|
81
|
+
# @overload set(value)
|
82
|
+
# @param value [String] time in ISO 8601
|
83
|
+
# @overload set(value)
|
84
|
+
# @param value [Integer] time in nanoseconds since UNIX epoch
|
85
|
+
# @raise [TypeError]
|
86
|
+
# @raise [ArgumentError]
|
87
|
+
def set(value)
|
88
|
+
if value.kind_of? Time or value.kind_of? DateTime or value.kind_of? Date
|
89
|
+
@value = time.to_datetime
|
90
|
+
elsif value.kind_of? String
|
91
|
+
@value = DateTime.parse(value)
|
92
|
+
elsif value.kind_of? Integer
|
93
|
+
@value = DateTime.new(value)
|
94
|
+
else
|
95
|
+
raise TypeError
|
96
|
+
end
|
97
|
+
end
|
60
98
|
|
61
99
|
end
|
62
100
|
|