ynl 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +3 -0
- data/Rakefile +0 -1
- data/lib/ynl/family.rb +9 -7
- data/lib/ynl/generator.rb +167 -43
- data/lib/ynl/models.rb +46 -4
- data/lib/ynl/parser.rb +124 -38
- data/lib/ynl/version.rb +3 -0
- data/lib/ynl.rb +57 -1
- metadata +8 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c66f9efc470db2a2d772016be6b06574e3551800fae411e2d0b80916d416481d
|
|
4
|
+
data.tar.gz: e204be8969be35b673a2257bc4d208c764a637e7e38d1e05bb22d7a1ea414132
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ba3b3866bb1d8aa066f5ab16438540ea1b5a505e8e489065df52388a3551b7333d1d58bfe66fc4d7a315dc78319511487f38367d323cbdf4db8a19333d1feb73
|
|
7
|
+
data.tar.gz: 3e57557c4fecb51d981099cde028caf80ad98928099428f4e6efff86be0be72e407a52f42d2e9a8fbf1f07e800053675b769eadcd2e9815901b4d937dd858770
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Kasumi Hanazuki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
CHANGED
data/lib/ynl/family.rb
CHANGED
|
@@ -5,16 +5,18 @@ require_relative 'generator'
|
|
|
5
5
|
|
|
6
6
|
module Ynl
|
|
7
7
|
class Family
|
|
8
|
+
# Builds a Ruby class from a spec file
|
|
8
9
|
def self.build(path)
|
|
9
|
-
require 'nl'
|
|
10
|
-
|
|
11
|
-
defs = Ynl::Parser.parse_file(path)
|
|
12
|
-
|
|
13
10
|
buf = StringIO.new
|
|
14
|
-
classname =
|
|
15
|
-
|
|
11
|
+
classname = File.open(path) {|f| generate(f, buf, namespace: 'self') }
|
|
12
|
+
|
|
13
|
+
Module.new { eval(buf.string) }.const_get(classname)
|
|
14
|
+
end
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
# Generates Ruby code from a spec source
|
|
17
|
+
def self.generate(source, out, **kwargs)
|
|
18
|
+
out << Generator::PRELUDE
|
|
19
|
+
Generator.new(Ynl::Parser.new(source).parse, out).generate(**kwargs)
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
end
|
data/lib/ynl/generator.rb
CHANGED
|
@@ -5,15 +5,15 @@ module Ynl
|
|
|
5
5
|
|
|
6
6
|
refine(String) do
|
|
7
7
|
def as_const_name
|
|
8
|
-
split(WORD_DELIM).map(&:upcase).join('_')
|
|
8
|
+
split(WORD_DELIM).map(&:upcase).join('_').tap {|s| s.prepend('X_') unless s.start_with?(/[A-Z]/i) }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def as_class_name
|
|
12
|
-
split(WORD_DELIM).map(&:capitalize).join
|
|
12
|
+
split(WORD_DELIM).map(&:capitalize).join.tap {|s| s.prepend('X') unless s.start_with?(/[A-Z]/i) }
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def as_variable_name
|
|
16
|
-
split(WORD_DELIM).join('_')
|
|
16
|
+
split(WORD_DELIM).join('_').tap {|s| s.prepend('x_') unless s.start_with?(/[A-Z]/i) }
|
|
17
17
|
end
|
|
18
18
|
alias as_method_name as_variable_name
|
|
19
19
|
|
|
@@ -26,10 +26,17 @@ module Ynl
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
|
-
private_constant :Refinements
|
|
29
|
+
# private_constant :Refinements
|
|
30
30
|
|
|
31
31
|
using Refinements
|
|
32
32
|
|
|
33
|
+
PRELUDE = -<<~RUBY
|
|
34
|
+
# frozen_string_literal: true
|
|
35
|
+
# rbs_inline: enabled
|
|
36
|
+
# This code is generated by Ynl::Generator. DO NOT EDIT.
|
|
37
|
+
require 'nl'
|
|
38
|
+
RUBY
|
|
39
|
+
|
|
33
40
|
def initialize(ynl, out)
|
|
34
41
|
@ynl = ynl
|
|
35
42
|
@out = out
|
|
@@ -37,29 +44,54 @@ module Ynl
|
|
|
37
44
|
end
|
|
38
45
|
|
|
39
46
|
def generate(superclass: '::Nl::Family', namespace: nil)
|
|
47
|
+
@protocol = '::Nl::Protocols::' + (@ynl.protocol == 'netlink-raw' ? 'Raw' : 'Genl')
|
|
48
|
+
|
|
49
|
+
emit_comment(@ynl.doc)
|
|
50
|
+
|
|
40
51
|
classname = @ynl.name.as_class_name
|
|
41
52
|
emit_class([*namespace, classname].join('::'), superclass) do
|
|
42
|
-
|
|
43
|
-
write('PROTONUM = ', @ynl.protonum)
|
|
53
|
+
emit_const('NAME', @ynl.name.as_string_literal)
|
|
44
54
|
|
|
45
55
|
if @ynl.protocol == 'netlink-raw'
|
|
46
|
-
|
|
56
|
+
emit_const('PROTOCOL', "Ractor.make_shareable(::Nl::Protocols::Raw.new(#{@ynl.name.as_string_literal}, #{@ynl.protonum}))")
|
|
47
57
|
else
|
|
48
|
-
|
|
58
|
+
emit_const('PROTOCOL', "Ractor.make_shareable(::Nl::Protocols::Genl.new(#{@ynl.name.as_string_literal}))")
|
|
49
59
|
end
|
|
50
60
|
|
|
51
61
|
emit_module('Structs') do
|
|
52
62
|
@ynl.structs.each do |name, struct|
|
|
53
63
|
emit_comment(struct.doc)
|
|
54
|
-
write(name.as_class_name,
|
|
64
|
+
write(name.as_class_name, " = Struct.new(")
|
|
65
|
+
indent do
|
|
66
|
+
struct.members.each do |member|
|
|
67
|
+
write(member.name.as_variable_name.as_symbol_literal, ', #: ', member.type.rbs_type)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
write(')')
|
|
55
71
|
emit_class(name.as_class_name) do
|
|
56
|
-
|
|
72
|
+
emit_nodoc
|
|
73
|
+
emit_const(
|
|
74
|
+
'MEMBERS',
|
|
75
|
+
"Ractor.make_shareable({#{struct.members.map { "#{it.name.as_variable_name}: #{to_datatype(it.type, nil)}" }.join(', ') }})",
|
|
76
|
+
rbs: 'Hash[::Symbol, ::Nl::_DataType]',
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
emit_comment('Decodes the struct.')
|
|
80
|
+
emit_rbs_comment(
|
|
81
|
+
'decoder: ::Nl::Decoder',
|
|
82
|
+
'return: instance',
|
|
83
|
+
)
|
|
57
84
|
write('def self.decode(decoder)')
|
|
58
85
|
indent do
|
|
59
86
|
write('self.new(*MEMBERS.map {|name, datatype| datatype.decode(decoder) })')
|
|
60
87
|
end
|
|
61
88
|
write('end')
|
|
62
89
|
|
|
90
|
+
emit_comment('Encodes the struct.')
|
|
91
|
+
emit_rbs_comment(
|
|
92
|
+
'encoder: ::Nl::Encoder',
|
|
93
|
+
'return: void',
|
|
94
|
+
)
|
|
63
95
|
write('def encode(encoder)')
|
|
64
96
|
indent do
|
|
65
97
|
write('MEMBERS.each {|name, datatype| datatype.encode(encoder, self.public_send(name)) }')
|
|
@@ -70,56 +102,106 @@ module Ynl
|
|
|
70
102
|
end
|
|
71
103
|
|
|
72
104
|
emit_module('AttributeSets') do
|
|
105
|
+
deferred_consts = []
|
|
73
106
|
@ynl.attribute_sets.each do |name, attr_set|
|
|
74
107
|
emit_comment(attr_set.doc)
|
|
75
|
-
emit_class(name.as_class_name, '::
|
|
108
|
+
emit_class(name.as_class_name, @protocol + '::AttributeSet') do
|
|
76
109
|
emit_comment("Abstract class")
|
|
77
|
-
emit_class('Attribute', '::
|
|
110
|
+
emit_class('Attribute', @protocol + '::AttributeSet::Attribute') do
|
|
78
111
|
end
|
|
79
112
|
attr_set.attributes.each do |attr|
|
|
80
113
|
emit_comment(attr.doc)
|
|
81
114
|
emit_class(attr.name.as_class_name, 'Attribute') do
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
emit_const('TYPE', attr.value)
|
|
116
|
+
emit_const('NAME', attr.name.as_variable_name.as_symbol_literal)
|
|
117
|
+
if attr.type.is_a?(Types::NestedAttributes) ||
|
|
118
|
+
(attr.type.is_a?(Types::IndexedArray) && attr.type.sub_type.is_a?(Types::NestedAttributes))
|
|
119
|
+
deferred_consts << ["#{name.as_class_name}::#{attr.name.as_class_name}::DATATYPE", to_datatype(attr.type, attr.checks)]
|
|
120
|
+
else
|
|
121
|
+
emit_const('DATATYPE', to_datatype(attr.type, attr.checks))
|
|
122
|
+
end
|
|
85
123
|
end
|
|
86
124
|
end
|
|
87
125
|
|
|
88
|
-
|
|
89
|
-
|
|
126
|
+
emit_nodoc
|
|
127
|
+
emit_const(
|
|
128
|
+
'BY_NAME',
|
|
129
|
+
"Ractor.make_shareable({#{attr_set.attributes.map { "#{it.name.as_variable_name.as_symbol_literal} => #{it.name.as_class_name}" }.join(', ') }})",
|
|
130
|
+
rbs: 'Hash[::Symbol, Attribute]',
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
emit_nodoc
|
|
134
|
+
emit_const(
|
|
135
|
+
'BY_TYPE',
|
|
136
|
+
"Ractor.make_shareable({#{attr_set.attributes.map { "#{it.value} => #{it.name.as_class_name}" }.join(', ') }})",
|
|
137
|
+
rbs: 'Hash[::Integer, Attribute]',
|
|
138
|
+
)
|
|
90
139
|
|
|
91
140
|
emit_singleton_class do
|
|
141
|
+
emit_comment('Looks up Attribute class by name.')
|
|
92
142
|
emit_rbs_comment(
|
|
93
143
|
'name: Symbol',
|
|
94
144
|
'return: Attribute',
|
|
95
145
|
)
|
|
96
|
-
|
|
146
|
+
emit_getter('by_name(name)', 'BY_NAME.fetch(name)')
|
|
97
147
|
|
|
148
|
+
emit_comment('Looks up Attribute class by type value.')
|
|
98
149
|
emit_rbs_comment(
|
|
99
150
|
'type: Integer',
|
|
100
151
|
'return: Attribute',
|
|
101
152
|
)
|
|
102
|
-
|
|
153
|
+
emit_getter('by_type(type)', 'BY_TYPE.fetch(type)')
|
|
103
154
|
end
|
|
104
155
|
end
|
|
105
156
|
end
|
|
157
|
+
deferred_consts.each { emit_const(*it) }
|
|
106
158
|
end
|
|
107
159
|
|
|
108
160
|
emit_module('Messages') do
|
|
109
161
|
@ynl.operations.each do |name, oper|
|
|
110
|
-
emit_comment(oper.doc)
|
|
111
162
|
%w[do dump].each do |method|
|
|
112
163
|
if request_reply = oper.public_send(method + 'it')
|
|
113
164
|
%w[request reply].to_h { [it, request_reply.public_send(it)] }.compact.each do |type, msg|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
165
|
+
emit_comment(oper.doc)
|
|
166
|
+
emit_class(method.as_class_name + oper.name.as_class_name + type.as_class_name, "#{@protocol}::Message") do
|
|
167
|
+
emit_const('TYPE', msg.value)
|
|
168
|
+
emit_const('FIXED_HEADER', oper.fixed_header&.then { 'Structs::' + it.name.as_class_name } || 'nil')
|
|
169
|
+
emit_const('ATTRIBUTE_SET', "AttributeSets::#{oper.attribute_set.name.as_class_name}")
|
|
118
170
|
params = msg.attributes
|
|
119
|
-
|
|
120
|
-
attribute_params
|
|
121
|
-
|
|
122
|
-
|
|
171
|
+
attribute_params = oper.attribute_set.attributes.map(&:name) & params
|
|
172
|
+
emit_const('ATTRIBUTES', "Ractor.make_shareable(%i[#{attribute_params.map { it.as_variable_name }.join(' ')}])")
|
|
173
|
+
oper.fixed_header.members.each do |member|
|
|
174
|
+
param = member.name
|
|
175
|
+
datatype = member.type
|
|
176
|
+
next if datatype.is_a? Types::Pad
|
|
177
|
+
next if attribute_params.include?(param)
|
|
178
|
+
emit_comment("Gets the value of `#{param}` field in the message's fixed header.")
|
|
179
|
+
emit_rbs_comment(
|
|
180
|
+
'return: ' + datatype.rbs_type,
|
|
181
|
+
)
|
|
182
|
+
emit_getter(param.as_method_name, "fixed_header.#{param.as_method_name}")
|
|
183
|
+
end if oper.fixed_header
|
|
184
|
+
attribute_params.each do |param|
|
|
185
|
+
datatype = oper.attribute_set.attributes.find { it.name == param }.type
|
|
186
|
+
next if datatype.is_a? Types::Pad
|
|
187
|
+
extending = oper.fixed_header&.members&.any? { it.name == param }
|
|
188
|
+
|
|
189
|
+
if extending
|
|
190
|
+
emit_comment("Gets the value of `#{param}` attribute or fixed header in the message.")
|
|
191
|
+
else
|
|
192
|
+
emit_comment("Gets the value of `#{param}` attribute in the message.")
|
|
193
|
+
end
|
|
194
|
+
emit_rbs_comment(
|
|
195
|
+
'return: ' + datatype.rbs_type,
|
|
196
|
+
)
|
|
197
|
+
if extending
|
|
198
|
+
# If the fixed header and the attribute set have the same-name parameter, the value from the attribute should have
|
|
199
|
+
# the precedence over that from the header.
|
|
200
|
+
emit_getter(param.as_method_name, "attributes[#{param.as_variable_name.as_symbol_literal}]&.value || fixed_header.#{param.as_method_name}")
|
|
201
|
+
else
|
|
202
|
+
emit_getter(param.as_method_name, "attributes[#{param.as_variable_name.as_symbol_literal}]&.value")
|
|
203
|
+
end
|
|
204
|
+
end
|
|
123
205
|
end
|
|
124
206
|
end
|
|
125
207
|
end
|
|
@@ -131,16 +213,37 @@ module Ynl
|
|
|
131
213
|
@ynl.operations.each do |name, oper|
|
|
132
214
|
%w[do dump].each do |method|
|
|
133
215
|
if request_reply = oper.public_send(method + 'it')
|
|
216
|
+
next unless request = request_reply.request # FIXME: what should we do in this case?
|
|
217
|
+
|
|
218
|
+
request_class = "Messages::#{method.as_class_name}#{oper.name.as_class_name}Request"
|
|
219
|
+
reply_class = request_reply.reply ? "Messages::#{method.as_class_name}#{oper.name.as_class_name}Reply" : 'nil'
|
|
220
|
+
|
|
221
|
+
header_params = oper.fixed_header&.members || []
|
|
222
|
+
attribute_params = oper.attribute_set.attributes.filter { request.attributes.include?(it.name) }
|
|
223
|
+
attribute_params.reject! {|a| header_params.any? {|h| h.name == a.name } } # The two params should have the same Ruby type anyway
|
|
224
|
+
params = header_params + attribute_params
|
|
225
|
+
params.reject! { it.type.is_a? Types::Pad }
|
|
226
|
+
|
|
227
|
+
rbs_params = params.map { |it| "?#{it.name.as_variable_name}: #{it.type.rbs_type}" }.join(', ')
|
|
228
|
+
rbs_result = request_reply.reply ? reply_class : 'void'
|
|
229
|
+
|
|
134
230
|
emit_comment(oper.doc)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
reply_class
|
|
231
|
+
if method == 'dump'
|
|
232
|
+
emit_rbs_comment(
|
|
233
|
+
"(#{rbs_params}) -> Enumerable[#{rbs_result}]\n | (#{rbs_params}) { (#{rbs_result}) -> void } -> void",
|
|
234
|
+
)
|
|
235
|
+
write("def #{method.as_method_name}_#{oper.name.as_method_name}(**args, &block)")
|
|
236
|
+
indent do
|
|
237
|
+
write("exchange_message(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args, &block)")
|
|
238
|
+
end
|
|
239
|
+
else
|
|
240
|
+
emit_rbs_comment(
|
|
241
|
+
"(#{rbs_params}) -> #{rbs_result}",
|
|
242
|
+
)
|
|
243
|
+
write("def #{method.as_method_name}_#{oper.name.as_method_name}(**args)")
|
|
244
|
+
indent do
|
|
245
|
+
write("exchange_message(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args)")
|
|
142
246
|
end
|
|
143
|
-
write("exchange_message(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args)")
|
|
144
247
|
end
|
|
145
248
|
write('end')
|
|
146
249
|
end
|
|
@@ -188,35 +291,56 @@ module Ynl
|
|
|
188
291
|
write('end')
|
|
189
292
|
end
|
|
190
293
|
|
|
294
|
+
private def emit_const(name, value, rbs: nil)
|
|
295
|
+
write(name, ' = ', value, *([' #: ', rbs] if rbs))
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
private def emit_getter(name, expr)
|
|
299
|
+
write('def ', name, '; ', expr, '; end')
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
private def emit_nodoc
|
|
303
|
+
write('# :nodoc:')
|
|
304
|
+
end
|
|
305
|
+
|
|
191
306
|
private def emit_comment(comment)
|
|
192
307
|
return unless comment
|
|
193
|
-
comment.each_line do |line|
|
|
308
|
+
comment.each_line(chomp: true) do |line|
|
|
194
309
|
write('# ', line)
|
|
195
310
|
end
|
|
196
311
|
end
|
|
197
312
|
|
|
198
313
|
private def emit_rbs_comment(*args)
|
|
314
|
+
write('#--')
|
|
199
315
|
args.each do |arg|
|
|
200
|
-
|
|
316
|
+
emit_comment('@rbs ' + arg)
|
|
201
317
|
end
|
|
202
318
|
end
|
|
203
319
|
|
|
204
320
|
private def to_datatype(type, checks)
|
|
205
321
|
case type
|
|
206
322
|
when Types::Pad
|
|
207
|
-
|
|
323
|
+
"#{@protocol}::DataTypes::Pad.new(#{type.length})"
|
|
324
|
+
when Types::Flag
|
|
325
|
+
"#{@protocol}::DataTypes::Flag.new"
|
|
326
|
+
when Types::Bitfield32
|
|
327
|
+
"#{@protocol}::DataTypes::Bitfield32.new"
|
|
208
328
|
when Types::Scalar
|
|
209
|
-
"
|
|
329
|
+
"#{@protocol}::DataTypes::Scalar.new(::Nl::Endian::#{type.byte_order.name.as_class_name}::#{type.type.as_const_name}, check: #{to_checks(checks)})"
|
|
210
330
|
when Types::String
|
|
211
|
-
"
|
|
331
|
+
"#{@protocol}::DataTypes::String.new(check: #{to_checks(checks)})"
|
|
212
332
|
when Types::Binary
|
|
213
333
|
# if type.struct
|
|
214
334
|
# "Structs::" + type.struct.name.as_class_name
|
|
215
335
|
# else
|
|
216
|
-
"
|
|
336
|
+
"#{@protocol}::DataTypes::Binary.new(check: #{to_checks(checks)})"
|
|
217
337
|
# end
|
|
218
338
|
when Types::NestedAttributes
|
|
219
|
-
"
|
|
339
|
+
"#{@protocol}::DataTypes::NestedAttributes.new(#{type.attribute_set.name.as_class_name})"
|
|
340
|
+
when Types::IndexedArray
|
|
341
|
+
"#{@protocol}::DataTypes::IndexedArray.new(#{to_datatype(type.sub_type, nil)})"
|
|
342
|
+
when Types::SubMessage
|
|
343
|
+
"#{@protocol}::DataTypes::Binary.new(check: nil)"
|
|
220
344
|
else
|
|
221
345
|
raise "Unknown type: #{type.class}"
|
|
222
346
|
end
|
data/lib/ynl/models.rb
CHANGED
|
@@ -27,6 +27,23 @@ module Ynl
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
Const = ::Struct.new(:name, :value, :doc)
|
|
31
|
+
|
|
32
|
+
class SubMessage
|
|
33
|
+
Format = ::Struct.new(:value, :attribute_set)
|
|
34
|
+
|
|
35
|
+
attr_reader :name, :formats
|
|
36
|
+
|
|
37
|
+
def initialize(name:)
|
|
38
|
+
@name = name
|
|
39
|
+
@formats = []
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def resolve(f)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
30
47
|
class Enum
|
|
31
48
|
Entry = ::Struct.new(:name, :value, :doc)
|
|
32
49
|
|
|
@@ -106,6 +123,31 @@ module Ynl
|
|
|
106
123
|
end
|
|
107
124
|
end
|
|
108
125
|
|
|
126
|
+
class AttributeSubset
|
|
127
|
+
attr_reader :name, :superset, :doc
|
|
128
|
+
|
|
129
|
+
def initialize(name:, superset:, attributes:, doc:)
|
|
130
|
+
@name = name
|
|
131
|
+
@superset = superset
|
|
132
|
+
@attributes = attributes
|
|
133
|
+
@doc = doc
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def attributes
|
|
137
|
+
# WORKAROUND: Some upstream specs (e.g. devlink) contain duplicate attribute
|
|
138
|
+
# entries in subset-of attribute sets. Deduplicate by name here until the
|
|
139
|
+
# upstream specs are fixed.
|
|
140
|
+
@attributes.uniq {|attr| attr.fetch('name') }.map do |attr|
|
|
141
|
+
name = attr.fetch('name')
|
|
142
|
+
superset.attributes.find { it.name == name } or raise ParseError "No attribute with name #{name}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def resolve(f)
|
|
147
|
+
self
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
109
151
|
class Operation
|
|
110
152
|
attr_reader :name, :doc, :fixed_header, :attribute_set, :doit, :dumpit
|
|
111
153
|
|
|
@@ -119,8 +161,8 @@ module Ynl
|
|
|
119
161
|
end
|
|
120
162
|
|
|
121
163
|
def resolve(f)
|
|
122
|
-
@doit = @doit
|
|
123
|
-
@dumpit = @dumpit
|
|
164
|
+
@doit = @doit&.resolve(f)
|
|
165
|
+
@dumpit = @dumpit&.resolve(f)
|
|
124
166
|
self
|
|
125
167
|
end
|
|
126
168
|
end
|
|
@@ -134,8 +176,8 @@ module Ynl
|
|
|
134
176
|
end
|
|
135
177
|
|
|
136
178
|
def resolve(f)
|
|
137
|
-
@request = @request
|
|
138
|
-
@reply = @reply
|
|
179
|
+
@request = @request&.resolve(f)
|
|
180
|
+
@reply = @reply&.resolve(f)
|
|
139
181
|
self
|
|
140
182
|
end
|
|
141
183
|
end
|
data/lib/ynl/parser.rb
CHANGED
|
@@ -26,7 +26,7 @@ module Ynl
|
|
|
26
26
|
protocol = @yaml['protocol'] || 'genetlink'
|
|
27
27
|
protonum = @yaml['protonum']
|
|
28
28
|
name = @yaml['name']
|
|
29
|
-
doc = @yaml['doc']
|
|
29
|
+
doc = translate_doc(@yaml['doc'])
|
|
30
30
|
|
|
31
31
|
@yaml['definitions']&.each do |d|
|
|
32
32
|
parse_definition(d)
|
|
@@ -41,14 +41,42 @@ module Ynl
|
|
|
41
41
|
enum_model = case operations['enum-model']
|
|
42
42
|
when 'directional'
|
|
43
43
|
:directional
|
|
44
|
-
when nil
|
|
45
|
-
:
|
|
44
|
+
when 'unified', nil
|
|
45
|
+
:unified
|
|
46
46
|
else
|
|
47
47
|
raise ParseError, "Unknown enum model: #{operations['enum-model']}"
|
|
48
48
|
end
|
|
49
49
|
@default_fixed_header = operations['fixed-header']&.then { @structs.fetch(it) }
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
|
|
51
|
+
if enum_model == :unified
|
|
52
|
+
counter = 0
|
|
53
|
+
operations['list']&.each do |d|
|
|
54
|
+
counter = d['value'] || (counter + 1)
|
|
55
|
+
parse_operation(d, request_id: counter, reply_id: counter)
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
request_counter = 1
|
|
59
|
+
reply_counter = 1
|
|
60
|
+
operations['list']&.each do |d|
|
|
61
|
+
if d.key?('notify') || d.key?('event')
|
|
62
|
+
reply_counter = d['value'] || reply_counter
|
|
63
|
+
parse_operation(d, request_id: nil, reply_id: reply_counter)
|
|
64
|
+
reply_counter += 1
|
|
65
|
+
else
|
|
66
|
+
primary = d['do'] || d['dump']
|
|
67
|
+
request_counter = primary&.dig('request', 'value') || request_counter
|
|
68
|
+
request_id = request_counter
|
|
69
|
+
request_counter += 1
|
|
70
|
+
if primary&.key?('reply')
|
|
71
|
+
reply_counter = primary.dig('reply', 'value') || reply_counter
|
|
72
|
+
reply_id = reply_counter
|
|
73
|
+
reply_counter += 1
|
|
74
|
+
else
|
|
75
|
+
reply_id = nil
|
|
76
|
+
end
|
|
77
|
+
parse_operation(d, request_id:, reply_id:)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
52
80
|
end
|
|
53
81
|
end
|
|
54
82
|
@yaml['mcast-groups']&.each do |d|
|
|
@@ -90,9 +118,14 @@ module Ynl
|
|
|
90
118
|
end
|
|
91
119
|
end
|
|
92
120
|
|
|
121
|
+
private def parse_const(d)
|
|
122
|
+
name = "#{d['name-prefix']}#{d.fetch('name')}"
|
|
123
|
+
Models::Const.new(name, d.fetch('value'), translate_doc(d['doc']))
|
|
124
|
+
end
|
|
125
|
+
|
|
93
126
|
private def parse_enum_flags(d, type:)
|
|
94
127
|
cls = type == :enum ? Models::Enum : Models::Flags
|
|
95
|
-
result = cls.new(name: d.fetch('name'), doc: d['doc'])
|
|
128
|
+
result = cls.new(name: d.fetch('name'), doc: translate_doc(d['doc']))
|
|
96
129
|
|
|
97
130
|
start_value = d['start-value'] || 0
|
|
98
131
|
value = type == :enum ? start_value : 1 << start_value
|
|
@@ -102,7 +135,7 @@ module Ynl
|
|
|
102
135
|
when String
|
|
103
136
|
entry = cls::Entry.new(name: v, value:)
|
|
104
137
|
when Hash
|
|
105
|
-
entry = cls::Entry.new(name: v.fetch('name'), value:, doc: v['doc'])
|
|
138
|
+
entry = cls::Entry.new(name: v.fetch('name'), value:, doc: translate_doc(v['doc']))
|
|
106
139
|
else
|
|
107
140
|
raise ParseError, "Unknown class for enum/flags entry: #{v.class}"
|
|
108
141
|
end
|
|
@@ -145,7 +178,7 @@ module Ynl
|
|
|
145
178
|
private def parse_attribute_type(d)
|
|
146
179
|
type = d.fetch('type')
|
|
147
180
|
case type
|
|
148
|
-
when 'u8', 'u16', 'u32', 'u64', 's8', 's16', 's32', 's64', 'int', 'uint'
|
|
181
|
+
when 'u8', 'u16', 'u32', 'u64', 's8', 's16', 's32', 's64', 'int', 'uint', 'sint'
|
|
149
182
|
Types::Scalar.new(
|
|
150
183
|
type: type,
|
|
151
184
|
byte_order: parse_byte_order(d['byte-order']),
|
|
@@ -161,6 +194,18 @@ module Ynl
|
|
|
161
194
|
Types::NestedAttributes.new(
|
|
162
195
|
attribute_set: Models::Thunk.new {|f| f.attribute_sets.fetch(d.fetch('nested-attributes')) },
|
|
163
196
|
)
|
|
197
|
+
when 'indexed-array'
|
|
198
|
+
Types::IndexedArray.new(
|
|
199
|
+
sub_type: parse_indexed_array_sub_type(d),
|
|
200
|
+
)
|
|
201
|
+
when 'nest-type-value'
|
|
202
|
+
if d['nested-attributes']
|
|
203
|
+
Types::NestedAttributes.new(
|
|
204
|
+
attribute_set: Models::Thunk.new {|f| f.attribute_sets.fetch(d.fetch('nested-attributes')) },
|
|
205
|
+
)
|
|
206
|
+
else
|
|
207
|
+
Types::Binary.new(struct: nil, display_hint: nil)
|
|
208
|
+
end
|
|
164
209
|
when 'sub-message'
|
|
165
210
|
Types::SubMessage.new(
|
|
166
211
|
sub_message: Models::Thunk.new {|f| f.sub_messages.fetch(d.fetch('sub-message')) },
|
|
@@ -170,6 +215,10 @@ module Ynl
|
|
|
170
215
|
Types::Pad.new(
|
|
171
216
|
length: nil,
|
|
172
217
|
)
|
|
218
|
+
when 'flag'
|
|
219
|
+
Types::Flag.new
|
|
220
|
+
when 'bitfield32'
|
|
221
|
+
Types::Bitfield32.new
|
|
173
222
|
when 'unused'
|
|
174
223
|
nil
|
|
175
224
|
else
|
|
@@ -177,6 +226,28 @@ module Ynl
|
|
|
177
226
|
end
|
|
178
227
|
end
|
|
179
228
|
|
|
229
|
+
private def parse_indexed_array_sub_type(d)
|
|
230
|
+
sub_type = d.fetch('sub-type')
|
|
231
|
+
case sub_type
|
|
232
|
+
when 'u8', 'u16', 'u32', 'u64', 's8', 's16', 's32', 's64', 'int', 'uint', 'sint'
|
|
233
|
+
Types::Scalar.new(
|
|
234
|
+
type: sub_type,
|
|
235
|
+
byte_order: parse_byte_order(d['byte-order']),
|
|
236
|
+
)
|
|
237
|
+
when 'binary'
|
|
238
|
+
Types::Binary.new(
|
|
239
|
+
struct: nil,
|
|
240
|
+
display_hint: d['display-hint'],
|
|
241
|
+
)
|
|
242
|
+
when 'nest'
|
|
243
|
+
Types::NestedAttributes.new(
|
|
244
|
+
attribute_set: Models::Thunk.new {|f| f.attribute_sets.fetch(d.fetch('nested-attributes')) },
|
|
245
|
+
)
|
|
246
|
+
else
|
|
247
|
+
raise ParseError, "Unknown indexed-array sub-type: #{sub_type}"
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
180
251
|
private def parse_byte_order(v)
|
|
181
252
|
case v
|
|
182
253
|
when nil
|
|
@@ -191,11 +262,11 @@ module Ynl
|
|
|
191
262
|
end
|
|
192
263
|
|
|
193
264
|
private def parse_struct(d)
|
|
194
|
-
result = Models::Struct.new(name: d.fetch('name'), doc: d['doc'])
|
|
265
|
+
result = Models::Struct.new(name: d.fetch('name'), doc: translate_doc(d['doc']))
|
|
195
266
|
|
|
196
267
|
d.fetch('members').each do |v|
|
|
197
268
|
type = parse_struct_member_type(v)
|
|
198
|
-
member = Models::Struct::Member.new(name: v.fetch('name'), type: type, doc: v['doc'])
|
|
269
|
+
member = Models::Struct::Member.new(name: v.fetch('name'), type: type, doc: translate_doc(v['doc']))
|
|
199
270
|
result.members << member
|
|
200
271
|
rescue
|
|
201
272
|
raise ParseError, "Failed to parse struct member: #{v.fetch('name')}"
|
|
@@ -207,7 +278,7 @@ module Ynl
|
|
|
207
278
|
end
|
|
208
279
|
|
|
209
280
|
private def parse_checks(d)
|
|
210
|
-
d.
|
|
281
|
+
d.filter_map do |op, value|
|
|
211
282
|
parse_check(op, value)
|
|
212
283
|
end
|
|
213
284
|
rescue
|
|
@@ -218,16 +289,21 @@ module Ynl
|
|
|
218
289
|
case op
|
|
219
290
|
when 'max'
|
|
220
291
|
value = parse_value(value_literal)
|
|
221
|
-
return %{raise unless it <= #{value}}
|
|
292
|
+
return %{raise ArgumentError, "Value \#{it.inspect} is greater than maximum #{value}" unless it <= #{value}}
|
|
222
293
|
when 'min'
|
|
223
294
|
value = parse_value(value_literal)
|
|
224
|
-
return %{raise unless it >= #{value}}
|
|
295
|
+
return %{raise ArgumentError, "Value \#{it.inspect} is less than minimum #{value}" unless it >= #{value}}
|
|
225
296
|
when 'min-len'
|
|
226
297
|
value = parse_value(value_literal)
|
|
227
|
-
return %{raise unless it.bytesize >= #{value}}
|
|
298
|
+
return %{raise ArgumentError, "Value \#{it.inspect} is shorter than minimum length #{value}" unless it.bytesize >= #{value}}
|
|
228
299
|
when 'max-len'
|
|
229
300
|
value = parse_value(value_literal)
|
|
230
|
-
return %{raise unless it.bytesize <= #{value}}
|
|
301
|
+
return %{raise ArgumentError, "Value \#{it.inspect} is longer than maximum length #{value}" unless it.bytesize <= #{value}}
|
|
302
|
+
when 'exact-len'
|
|
303
|
+
value = parse_value(value_literal)
|
|
304
|
+
return %{raise ArgumentError, "Value \#{it.inspect} is not equal to length #{value}" unless it.bytesize == #{value}}
|
|
305
|
+
when 'unterminated-ok'
|
|
306
|
+
return nil
|
|
231
307
|
else
|
|
232
308
|
raise ParseError, "Unknown check: #{op}"
|
|
233
309
|
end
|
|
@@ -243,6 +319,9 @@ module Ynl
|
|
|
243
319
|
(2 ** 32) - 1
|
|
244
320
|
when 's32-max'
|
|
245
321
|
(2 ** 31) - 1
|
|
322
|
+
when String
|
|
323
|
+
const = @consts[v] or raise ParseError, "Unknown value: #{v}"
|
|
324
|
+
const.value
|
|
246
325
|
else
|
|
247
326
|
raise ParseError, "Unknown value: #{v}"
|
|
248
327
|
end
|
|
@@ -251,22 +330,11 @@ module Ynl
|
|
|
251
330
|
private def parse_attribute_set(d)
|
|
252
331
|
name = d.fetch('name')
|
|
253
332
|
if subset_of = d['subset-of']
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
attribute_set = Models::AttributeSet.new(name:, name_prefix: superset.name_prefix, doc: d['doc'])
|
|
257
|
-
d.fetch('attributes').each do |v|
|
|
258
|
-
aname = v.fetch('name')
|
|
259
|
-
sattr = superset.attributes.find {|a| a.name == aname } or raise ParseError, "Attribute not found: #{aname}"
|
|
260
|
-
# TODO: type/checks overrides
|
|
261
|
-
attribute_set.attributes << sattr
|
|
262
|
-
rescue
|
|
263
|
-
raise ParseError, "Failed to parse attribute: #{v.fetch('name')}"
|
|
264
|
-
end
|
|
265
|
-
attribute_set
|
|
266
|
-
end
|
|
333
|
+
superset = @attribute_sets.fetch(subset_of)
|
|
334
|
+
result = Models::AttributeSubset.new(name:, superset:, attributes: d.fetch('attributes'), doc: translate_doc(d['doc']))
|
|
267
335
|
else
|
|
268
336
|
name_prefix = d['name_prefix']
|
|
269
|
-
result = Models::AttributeSet.new(name:, name_prefix:, doc: d['doc'])
|
|
337
|
+
result = Models::AttributeSet.new(name:, name_prefix:, doc: translate_doc(d['doc']))
|
|
270
338
|
value = 0
|
|
271
339
|
|
|
272
340
|
d.fetch('attributes').each do |v|
|
|
@@ -290,9 +358,16 @@ module Ynl
|
|
|
290
358
|
end
|
|
291
359
|
|
|
292
360
|
private def parse_sub_message(d)
|
|
361
|
+
name = d.fetch('name')
|
|
362
|
+
result = Models::SubMessage.new(name:)
|
|
363
|
+
d.fetch('formats', []).each do |fmt|
|
|
364
|
+
attr_set = fmt['attribute-set']&.then { @attribute_sets[it] }
|
|
365
|
+
result.formats << Models::SubMessage::Format.new(fmt['value'], attr_set)
|
|
366
|
+
end
|
|
367
|
+
@sub_messages[name] = result
|
|
293
368
|
end
|
|
294
369
|
|
|
295
|
-
private def parse_operation(d,
|
|
370
|
+
private def parse_operation(d, request_id:, reply_id:)
|
|
296
371
|
name = d.fetch('name')
|
|
297
372
|
|
|
298
373
|
fixed_header = d['fixed-header']&.then do
|
|
@@ -307,24 +382,35 @@ module Ynl
|
|
|
307
382
|
raise ParseError, "Undefined attribute set: #{it}"
|
|
308
383
|
end
|
|
309
384
|
|
|
310
|
-
doit = d['do']&.then { parse_request_reply(it) }
|
|
311
|
-
dumpit = d['dump']&.then { parse_request_reply(it) }
|
|
385
|
+
doit = d['do']&.then { parse_request_reply(it, default_request_id: request_id, default_reply_id: reply_id) }
|
|
386
|
+
dumpit = d['dump']&.then { parse_request_reply(it, default_request_id: request_id, default_reply_id: reply_id) }
|
|
387
|
+
|
|
388
|
+
# TODO: notify
|
|
312
389
|
|
|
313
|
-
@operations[name] = Models::Operation.new(
|
|
390
|
+
@operations[name] = Models::Operation.new(
|
|
391
|
+
name:, doc: translate_doc(d['doc']), fixed_header:, attribute_set:,
|
|
392
|
+
doit:, dumpit:,
|
|
393
|
+
)
|
|
314
394
|
end
|
|
315
395
|
|
|
316
|
-
private def parse_request_reply(d)
|
|
396
|
+
private def parse_request_reply(d, default_request_id:, default_reply_id:)
|
|
317
397
|
Models::RequestReply.new(
|
|
318
|
-
request: d['request']&.then { parse_message(it) },
|
|
319
|
-
reply: d['reply']&.then { parse_message(it) },
|
|
398
|
+
request: d['request']&.then { parse_message(it, default_id: default_request_id) },
|
|
399
|
+
reply: d['reply']&.then { parse_message(it, default_id: default_reply_id) },
|
|
320
400
|
)
|
|
321
401
|
end
|
|
322
402
|
|
|
323
|
-
private def parse_message(d)
|
|
324
|
-
Models::Message.new(value: d['value'], attributes: d.fetch('attributes', []))
|
|
403
|
+
private def parse_message(d, default_id:)
|
|
404
|
+
Models::Message.new(value: d['value'] || default_id, attributes: d.fetch('attributes', []))
|
|
325
405
|
end
|
|
326
406
|
|
|
327
407
|
private def parse_mcast_group(d)
|
|
328
408
|
end
|
|
409
|
+
|
|
410
|
+
private def translate_doc(doc)
|
|
411
|
+
return unless doc
|
|
412
|
+
|
|
413
|
+
doc.gsub(/(?:\s|^)\K@(?<ident>[\w.-]+)/i) { "`#{$~[:ident]}`" }
|
|
414
|
+
end
|
|
329
415
|
end
|
|
330
416
|
end
|
data/lib/ynl/version.rb
ADDED
data/lib/ynl.rb
CHANGED
|
@@ -12,36 +12,92 @@ module Ynl
|
|
|
12
12
|
def resolve(f)
|
|
13
13
|
self
|
|
14
14
|
end
|
|
15
|
+
|
|
16
|
+
def rbs_type
|
|
17
|
+
'::Integer'
|
|
18
|
+
end
|
|
15
19
|
end
|
|
16
20
|
String = Struct.new do
|
|
17
21
|
def resolve(f)
|
|
18
22
|
self
|
|
19
23
|
end
|
|
24
|
+
|
|
25
|
+
def rbs_type
|
|
26
|
+
'::String'
|
|
27
|
+
end
|
|
20
28
|
end
|
|
21
29
|
Binary = Struct.new(:struct, :length, :display_hint) do
|
|
22
30
|
def resolve(f)
|
|
23
31
|
self.struct = struct.resolve(f) if self.struct
|
|
24
32
|
self
|
|
25
33
|
end
|
|
34
|
+
|
|
35
|
+
def rbs_type
|
|
36
|
+
'untyped'
|
|
37
|
+
end
|
|
26
38
|
end
|
|
27
39
|
NestedAttributes = Struct.new(:attribute_set) do
|
|
40
|
+
using Generator::Refinements # FIXME:
|
|
41
|
+
|
|
28
42
|
def resolve(f)
|
|
29
43
|
self.attribute_set = attribute_set.resolve(f)
|
|
30
44
|
self
|
|
31
45
|
end
|
|
46
|
+
|
|
47
|
+
def rbs_type
|
|
48
|
+
'AttributeSets::' + attribute_set.name.as_class_name
|
|
49
|
+
end
|
|
32
50
|
end
|
|
33
51
|
SubMessage = Struct.new(:sub_message, :selector) do
|
|
34
52
|
def resolve(f)
|
|
35
53
|
self.sub_message = sub_message.resolve(f)
|
|
36
54
|
self
|
|
37
55
|
end
|
|
56
|
+
|
|
57
|
+
def rbs_type
|
|
58
|
+
'untyped'
|
|
59
|
+
end
|
|
38
60
|
end
|
|
39
61
|
|
|
40
62
|
Pad = Data.define(:length) do
|
|
41
63
|
def resolve(f)
|
|
42
64
|
self
|
|
43
65
|
end
|
|
66
|
+
|
|
67
|
+
def rbs_type
|
|
68
|
+
'nil'
|
|
69
|
+
end
|
|
44
70
|
end
|
|
45
|
-
end
|
|
46
71
|
|
|
72
|
+
Flag = Data.define do
|
|
73
|
+
def resolve(f)
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def rbs_type
|
|
78
|
+
'::Integer'
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
Bitfield32 = Data.define do
|
|
83
|
+
def resolve(f)
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def rbs_type
|
|
88
|
+
'::Integer'
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
IndexedArray = Struct.new(:sub_type) do
|
|
93
|
+
def resolve(f)
|
|
94
|
+
self.sub_type = sub_type.resolve(f)
|
|
95
|
+
self
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def rbs_type
|
|
99
|
+
'untyped'
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
47
103
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ynl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kasumi Hanazuki
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.1
|
|
18
|
+
version: 0.2.1
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.1
|
|
25
|
+
version: 0.2.1
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: yaml
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -45,12 +45,16 @@ extensions: []
|
|
|
45
45
|
extra_rdoc_files: []
|
|
46
46
|
files:
|
|
47
47
|
- ".rspec"
|
|
48
|
+
- CHANGELOG.md
|
|
49
|
+
- LICENSE.txt
|
|
50
|
+
- README.md
|
|
48
51
|
- Rakefile
|
|
49
52
|
- lib/ynl.rb
|
|
50
53
|
- lib/ynl/family.rb
|
|
51
54
|
- lib/ynl/generator.rb
|
|
52
55
|
- lib/ynl/models.rb
|
|
53
56
|
- lib/ynl/parser.rb
|
|
57
|
+
- lib/ynl/version.rb
|
|
54
58
|
- test.rb
|
|
55
59
|
homepage: https://github.com/hanazuki/nl
|
|
56
60
|
licenses:
|
|
@@ -73,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
73
77
|
- !ruby/object:Gem::Version
|
|
74
78
|
version: '0'
|
|
75
79
|
requirements: []
|
|
76
|
-
rubygems_version:
|
|
80
|
+
rubygems_version: 4.0.3
|
|
77
81
|
specification_version: 4
|
|
78
82
|
summary: Linux Netlink - core
|
|
79
83
|
test_files: []
|