ynl 0.2.4 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e9f2afaf5ceef180b6fdfbe4e823d73f9f3a456b150f5b6bc1da9656ab3fdc4
4
- data.tar.gz: e85e2ac604e46a15a8bae4097255882aec8edbfb7413100403f1ff4b7a6f8d91
3
+ metadata.gz: e19f2ff09effa0411bc676e0eb2356129f8aa496ec4bbf1df66935268d4e6763
4
+ data.tar.gz: 72bfd23c64c44dd2db4b449e6863c7855e34c1ed2c774b5f2ff3e16f7fd7910f
5
5
  SHA512:
6
- metadata.gz: f22bce88df788cc8a332d8186cf93e25050ce459635dafbf13190739078c8325dae954123a41a5e1e84c4f60431678660373eee71890e3d6788a0f8ec4cdf708
7
- data.tar.gz: 1e700baaba42287f513733403c4d49e96130aeed616ac88f76ac0acd1dd7c123a552144534fb803fc57b5a57b394bd4dcb3e1bc2d464e17211df880738181019
6
+ metadata.gz: c3c294f02102bb2031e6681a17a5bd92dfd4bbde39db1c94302dd8775756d34b47d1bea3ff0a7c94f4f5a857569fc2c4a658ae80ff275c7b109094c0d9b208c7
7
+ data.tar.gz: 941d649b3233024963fabd1bd51d083b3ba22639dfb15e9b2e74df5264b948fb82ac152d86751f6352be28d2a4a283c10aba2dbddcd154744db810594bbee821
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## v0.3.0 (2026-09-01)
6
+
7
+ - Generate notification and event message classes, multicast group metadata,
8
+ and typed subscription methods.
9
+ - Generate nested type-value attributes with their declared type-key depth.
10
+ - Generate methods for operations with implicit empty requests.
11
+
5
12
  ## v0.2.4 (2026-08-22)
6
13
 
7
14
  - Assign correct values to attributes following `unused` entries.
data/lib/ynl/generator.rb CHANGED
@@ -37,6 +37,12 @@ module Ynl
37
37
  require 'nl'
38
38
  RUBY
39
39
 
40
+ OPERATION_FLAG_DOCS = {
41
+ 'admin-perm' => 'Requires CAP_NET_ADMIN in the initial user namespace.',
42
+ 'uns-admin-perm' => 'Requires CAP_NET_ADMIN in the user namespace owning the network namespace.',
43
+ }.freeze
44
+ private_constant :OPERATION_FLAG_DOCS
45
+
40
46
  def initialize(ynl, out)
41
47
  @ynl = ynl
42
48
  @out = out
@@ -58,6 +64,8 @@ module Ynl
58
64
  emit_const('PROTOCOL', "Ractor.make_shareable(::Nl::Protocols::Genl.new(#{@ynl.name.as_string_literal}))")
59
65
  end
60
66
 
67
+ emit_mcast_groups
68
+
61
69
  emit_module('Structs') do
62
70
  @ynl.structs.each do |name, struct|
63
71
  emit_comment(struct.doc)
@@ -115,6 +123,7 @@ module Ynl
115
123
  emit_const('TYPE', attr.value)
116
124
  emit_const('NAME', attr.name.as_variable_name.as_symbol_literal)
117
125
  if attr.type.is_a?(Types::NestedAttributes) ||
126
+ attr.type.is_a?(Types::NestTypeValue) ||
118
127
  (attr.type.is_a?(Types::IndexedArray) && attr.type.sub_type.is_a?(Types::NestedAttributes))
119
128
  deferred_consts << ["#{name.as_class_name}::#{attr.name.as_class_name}::DATATYPE", to_datatype(attr.type, attr.checks)]
120
129
  else
@@ -162,58 +171,43 @@ module Ynl
162
171
  %w[do dump].each do |method|
163
172
  if request_reply = oper.public_send(method + 'it')
164
173
  %w[request reply].to_h { [it, request_reply.public_send(it)] }.compact.each do |type, msg|
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}")
170
- params = msg.attributes
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
205
- end
174
+ emit_message_class(
175
+ method.as_class_name + oper.name.as_class_name + type.as_class_name,
176
+ msg,
177
+ fixed_header: oper.fixed_header,
178
+ attribute_set: oper.attribute_set,
179
+ doc: operation_doc(oper),
180
+ )
206
181
  end
207
182
  end
208
183
  end
209
184
  end
210
185
  end
211
186
 
187
+ emit_module('Notifications') do
188
+ notification_operations.each do |oper|
189
+ message, fixed_header, attribute_set = notification_layout(oper)
190
+ emit_message_class(
191
+ oper.name.as_class_name,
192
+ message,
193
+ fixed_header:,
194
+ attribute_set:,
195
+ doc: notification_doc(oper),
196
+ )
197
+ end
198
+ end
199
+
200
+ emit_const(
201
+ 'NOTIFICATIONS',
202
+ "Ractor.make_shareable({#{notification_operations.map { |oper| "#{oper.notification.message.value} => Notifications::#{oper.name.as_class_name}" }.join(', ') }})",
203
+ rbs: 'Hash[::Integer, ::Class]',
204
+ )
205
+
212
206
  # emit request methods
213
207
  @ynl.operations.each do |name, oper|
214
208
  %w[do dump].each do |method|
215
209
  if request_reply = oper.public_send(method + 'it')
216
- next unless request = request_reply.request # FIXME: what should we do in this case?
210
+ request = request_reply.request
217
211
 
218
212
  request_class = "Messages::#{method.as_class_name}#{oper.name.as_class_name}Request"
219
213
  reply_class = request_reply.reply ? "Messages::#{method.as_class_name}#{oper.name.as_class_name}Reply" : 'nil'
@@ -227,7 +221,7 @@ module Ynl
227
221
  rbs_params = params.map { |it| "?#{it.name.as_variable_name}: #{it.type.rbs_type}" }.join(', ')
228
222
  rbs_result = request_reply.reply ? reply_class : 'void'
229
223
 
230
- emit_comment(oper.doc)
224
+ emit_comment(operation_doc(oper))
231
225
  if method == 'dump'
232
226
  emit_rbs_comment(
233
227
  "(#{rbs_params}) -> Enumerable[#{rbs_result}]\n | (#{rbs_params}) { (#{rbs_result}) -> void } -> void",
@@ -249,6 +243,50 @@ module Ynl
249
243
  end
250
244
  end
251
245
  end
246
+
247
+ emit_class('AsyncOperations') do
248
+ emit_nodoc
249
+ write('def initialize(&exchange)')
250
+ indent do
251
+ write('@exchange = exchange')
252
+ end
253
+ write('end')
254
+
255
+ @ynl.operations.each do |name, oper|
256
+ %w[do dump].each do |method|
257
+ next unless request_reply = oper.public_send(method + 'it')
258
+
259
+ request_class = "Messages::#{method.as_class_name}#{oper.name.as_class_name}Request"
260
+ reply_class = request_reply.reply ? "Messages::#{method.as_class_name}#{oper.name.as_class_name}Reply" : 'nil'
261
+ header_params = oper.fixed_header&.members || []
262
+ attribute_params = oper.attribute_set.attributes.filter { request_reply.request.attributes.include?(it.name) }
263
+ attribute_params.reject! {|a| header_params.any? {|h| h.name == a.name } }
264
+ params = (header_params + attribute_params).reject { it.type.is_a? Types::Pad }
265
+ rbs_params = params.map { |it| "?#{it.name.as_variable_name}: #{it.type.rbs_type}" }.join(', ')
266
+ result = request_reply.reply ? reply_class : 'void'
267
+ operation = method == 'dump' ? "::Nl::Async::Stream[#{result}]" : "::Nl::Async::Future[#{result}]"
268
+
269
+ emit_comment(operation_doc(oper))
270
+ emit_rbs_comment("(#{rbs_params}) -> #{operation}")
271
+ write("def #{method.as_method_name}_#{oper.name.as_method_name}(**args)")
272
+ indent do
273
+ write("@exchange.call(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args)")
274
+ end
275
+ write('end')
276
+ end
277
+ end
278
+ end
279
+
280
+ emit_comment('Returns the asynchronous operation facade for this family.')
281
+ emit_rbs_comment('(?stream_capacity: ::Integer?) -> AsyncOperations')
282
+ write('def async(stream_capacity: nil)')
283
+ indent do
284
+ write('return @async_operations ||= build_async_facade(AsyncOperations) if stream_capacity.nil?')
285
+ write('build_async_facade(AsyncOperations, stream_capacity:)')
286
+ end
287
+ write('end')
288
+
289
+ emit_subscription_methods unless @ynl.mcast_groups.empty?
252
290
  end
253
291
 
254
292
  classname
@@ -310,6 +348,109 @@ module Ynl
310
348
  end
311
349
  end
312
350
 
351
+ private def operation_doc(operation)
352
+ flag_docs = operation.flags.filter_map { OPERATION_FLAG_DOCS[it] }
353
+ [operation.doc, *flag_docs].compact.join("\n\n")
354
+ end
355
+
356
+ private def notification_operations
357
+ @notification_operations ||= @ynl.operations.values.select(&:notification)
358
+ end
359
+
360
+ private def notification_doc(operation)
361
+ group = operation.notification.group
362
+ [operation.doc, ("Multicast group: `#{group.name}`." if group)].compact.join("\n\n")
363
+ end
364
+
365
+ private def notification_layout(operation)
366
+ notification = operation.notification
367
+ if notification.kind == :event
368
+ [notification.message, operation.fixed_header, operation.attribute_set]
369
+ else
370
+ source = notification.source
371
+ mode = source.doit || source.dumpit
372
+ reply = mode&.reply
373
+ unless reply
374
+ selected = source.doit ? 'do' : 'dump'
375
+ raise ParseError,
376
+ "Notification #{operation.name.inspect} references #{source.name.inspect}, which has no #{selected} reply"
377
+ end
378
+ message = Models::Message.new(
379
+ value: notification.message.value,
380
+ attributes: reply.attributes,
381
+ )
382
+ [message, source.fixed_header, source.attribute_set]
383
+ end
384
+ end
385
+
386
+ private def emit_message_class(name, message, fixed_header:, attribute_set:, doc:)
387
+ emit_comment(doc)
388
+ emit_class(name, "#{@protocol}::Message") do
389
+ emit_const('TYPE', message.value)
390
+ emit_const('FIXED_HEADER', fixed_header&.then { 'Structs::' + it.name.as_class_name } || 'nil')
391
+ emit_const('ATTRIBUTE_SET', "AttributeSets::#{attribute_set.name.as_class_name}")
392
+ attribute_params = attribute_set.attributes.map(&:name) & message.attributes
393
+ emit_const('ATTRIBUTES', "Ractor.make_shareable(%i[#{attribute_params.map { it.as_variable_name }.join(' ')}])")
394
+ fixed_header&.members&.each do |member|
395
+ param = member.name
396
+ datatype = member.type
397
+ next if datatype.is_a? Types::Pad
398
+ next if attribute_params.include?(param)
399
+ emit_comment("Gets the value of `#{param}` field in the message's fixed header.")
400
+ emit_rbs_comment('return: ' + datatype.rbs_type)
401
+ emit_getter(param.as_method_name, "fixed_header.#{param.as_method_name}")
402
+ end
403
+ attribute_params.each do |param|
404
+ datatype = attribute_set.attributes.find { it.name == param }.type
405
+ next if datatype.is_a? Types::Pad
406
+ extending = fixed_header&.members&.any? { it.name == param }
407
+
408
+ if extending
409
+ emit_comment("Gets the value of `#{param}` attribute or fixed header in the message.")
410
+ else
411
+ emit_comment("Gets the value of `#{param}` attribute in the message.")
412
+ end
413
+ emit_rbs_comment('return: ' + datatype.rbs_type)
414
+ if extending
415
+ emit_getter(param.as_method_name, "attributes[#{param.as_variable_name.as_symbol_literal}]&.value || fixed_header.#{param.as_method_name}")
416
+ else
417
+ emit_getter(param.as_method_name, "attributes[#{param.as_variable_name.as_symbol_literal}]&.value")
418
+ end
419
+ end
420
+ end
421
+ end
422
+
423
+ private def emit_mcast_groups
424
+ groups = @ynl.mcast_groups.values
425
+ grouped = groups.group_by { it.name.as_variable_name }
426
+ if collision = grouped.find { |_name, matches| matches.length > 1 }
427
+ ruby_name, matches = collision
428
+ raise ParseError,
429
+ "Multicast groups #{matches.map(&:name).map(&:inspect).join(' and ')} normalize to #{ruby_name.to_sym.inspect}"
430
+ end
431
+
432
+ entries = groups.map do |group|
433
+ key = group.name.as_variable_name.as_symbol_literal
434
+ value = "::Nl::McastGroup.new(#{group.name.as_string_literal}, #{group.value.inspect})"
435
+ "#{key} => #{value}"
436
+ end
437
+ emit_const(
438
+ 'MCAST_GROUPS',
439
+ "Ractor.make_shareable({#{entries.join(', ')}})",
440
+ rbs: 'Hash[::Symbol, ::Nl::McastGroup]',
441
+ )
442
+ end
443
+
444
+ private def emit_subscription_methods
445
+ group_type = @ynl.mcast_groups.values
446
+ .map { ":#{it.name.as_variable_name}" }
447
+ .join(' | ')
448
+ emit_rbs_comment("(*(#{group_type}) groups) -> self")
449
+ write('def subscribe(*groups) = super')
450
+ emit_rbs_comment("(*(#{group_type}) groups) -> self")
451
+ write('def unsubscribe(*groups) = super')
452
+ end
453
+
313
454
  private def emit_rbs_comment(*args)
314
455
  write('#--')
315
456
  args.each do |arg|
@@ -337,6 +478,8 @@ module Ynl
337
478
  # end
338
479
  when Types::NestedAttributes
339
480
  "#{@protocol}::DataTypes::NestedAttributes.new(#{type.attribute_set.name.as_class_name})"
481
+ when Types::NestTypeValue
482
+ "#{@protocol}::DataTypes::NestTypeValue.new(#{type.attribute_set.name.as_class_name}, #{type.type_values.length})"
340
483
  when Types::IndexedArray
341
484
  "#{@protocol}::DataTypes::IndexedArray.new(#{to_datatype(type.sub_type, nil)})"
342
485
  when Types::SubMessage
data/lib/ynl/models.rb CHANGED
@@ -7,6 +7,7 @@ module Ynl
7
7
  def resolve
8
8
  structs.transform_values! {|s| s.resolve(self) }
9
9
  attribute_sets.transform_values! {|s| s.resolve(self) }
10
+ operations.transform_values! {|operation| operation.resolve(self) }
10
11
  self
11
12
  end
12
13
 
@@ -148,22 +149,47 @@ module Ynl
148
149
  end
149
150
  end
150
151
 
152
+ McastGroup = ::Struct.new(:name, :value, :doc)
153
+
151
154
  class Operation
152
- attr_reader :name, :doc, :fixed_header, :attribute_set, :doit, :dumpit
155
+ attr_reader :name, :doc, :flags, :fixed_header, :attribute_set, :doit, :dumpit, :notification
153
156
 
154
- def initialize(name:, doc:, fixed_header:, attribute_set:, doit:, dumpit:)
157
+ def initialize(name:, doc:, flags:, fixed_header:, attribute_set:, doit:, dumpit:, notification:)
155
158
  @name = name
156
159
  @doc = doc
160
+ @flags = flags
157
161
  @fixed_header = fixed_header
158
162
  @attribute_set = attribute_set
159
163
  @doit = doit
160
164
  @dumpit = dumpit
165
+ @notification = notification
161
166
  end
162
167
 
163
168
  def resolve(f)
164
169
  @doit = @doit&.resolve(f)
165
170
  @dumpit = @dumpit&.resolve(f)
171
+ @notification = @notification&.resolve(f)
172
+ self
173
+ end
174
+ end
175
+
176
+ class Notification
177
+ attr_reader :kind, :message, :source, :group
178
+
179
+ def initialize(kind:, message:, source:, group:)
180
+ @kind = kind
181
+ @message = message
182
+ @source = source
183
+ @group = group
184
+ end
185
+
186
+ def resolve(f)
187
+ @message = @message.resolve(f)
188
+ @source = f.operations.fetch(@source) if @source
189
+ @group = f.mcast_groups.fetch(@group) if @group
166
190
  self
191
+ rescue KeyError => error
192
+ raise ParseError, "Failed to resolve #{kind} notification: #{error.message}"
167
193
  end
168
194
  end
169
195
 
data/lib/ynl/parser.rb CHANGED
@@ -79,7 +79,7 @@ module Ynl
79
79
  end
80
80
  end
81
81
  end
82
- @yaml['mcast-groups']&.each do |d|
82
+ @yaml.dig('mcast-groups', 'list')&.each do |d|
83
83
  parse_mcast_group(d)
84
84
  end
85
85
 
@@ -200,8 +200,9 @@ module Ynl
200
200
  )
201
201
  when 'nest-type-value'
202
202
  if d['nested-attributes']
203
- Types::NestedAttributes.new(
203
+ Types::NestTypeValue.new(
204
204
  attribute_set: Models::Thunk.new {|f| f.attribute_sets.fetch(d.fetch('nested-attributes')) },
205
+ type_values: d.fetch('type-value'),
205
206
  )
206
207
  else
207
208
  Types::Binary.new(struct: nil, display_hint: nil)
@@ -384,18 +385,35 @@ module Ynl
384
385
 
385
386
  doit = d['do']&.then { parse_request_reply(it, default_request_id: request_id, default_reply_id: reply_id) }
386
387
  dumpit = d['dump']&.then { parse_request_reply(it, default_request_id: request_id, default_reply_id: reply_id) }
387
-
388
- # TODO: notify
388
+ notification = parse_notification(d, default_id: reply_id)
389
389
 
390
390
  @operations[name] = Models::Operation.new(
391
- name:, doc: translate_doc(d['doc']), fixed_header:, attribute_set:,
392
- doit:, dumpit:,
391
+ name:, doc: translate_doc(d['doc']), flags: d.fetch('flags', []), fixed_header:, attribute_set:,
392
+ doit:, dumpit:, notification:,
393
393
  )
394
394
  end
395
395
 
396
+ private def parse_notification(d, default_id:)
397
+ if source = d['notify']
398
+ Models::Notification.new(
399
+ kind: :notify,
400
+ message: parse_message({}, default_id:),
401
+ source:,
402
+ group: d['mcgrp'],
403
+ )
404
+ elsif event = d['event']
405
+ Models::Notification.new(
406
+ kind: :event,
407
+ message: parse_message(event, default_id:),
408
+ source: nil,
409
+ group: d['mcgrp'],
410
+ )
411
+ end
412
+ end
413
+
396
414
  private def parse_request_reply(d, default_request_id:, default_reply_id:)
397
415
  Models::RequestReply.new(
398
- request: d['request']&.then { parse_message(it, default_id: default_request_id) },
416
+ request: parse_message(d.fetch('request', {}), default_id: default_request_id),
399
417
  reply: d['reply']&.then { parse_message(it, default_id: default_reply_id) },
400
418
  )
401
419
  end
@@ -405,6 +423,12 @@ module Ynl
405
423
  end
406
424
 
407
425
  private def parse_mcast_group(d)
426
+ name = d.fetch('name')
427
+ @mcast_groups[name] = Models::McastGroup.new(
428
+ name,
429
+ d['value'],
430
+ translate_doc(d['doc']),
431
+ )
408
432
  end
409
433
 
410
434
  private def translate_doc(doc)
data/lib/ynl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ynl
2
- VERSION = '0.2.4'
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/ynl.rb CHANGED
@@ -33,7 +33,7 @@ module Ynl
33
33
  end
34
34
 
35
35
  def rbs_type
36
- 'untyped'
36
+ '::String'
37
37
  end
38
38
  end
39
39
  NestedAttributes = Struct.new(:attribute_set) do
@@ -48,6 +48,20 @@ module Ynl
48
48
  'AttributeSets::' + attribute_set.name.as_class_name
49
49
  end
50
50
  end
51
+ NestTypeValue = Struct.new(:attribute_set, :type_values) do
52
+ using Generator::Refinements
53
+
54
+ def resolve(f)
55
+ self.attribute_set = attribute_set.resolve(f)
56
+ self
57
+ end
58
+
59
+ def rbs_type
60
+ type = 'AttributeSets::' + attribute_set.name.as_class_name
61
+ type_values.length.times { type = "::Hash[::Integer, #{type}]" }
62
+ type
63
+ end
64
+ end
51
65
  SubMessage = Struct.new(:sub_message, :selector) do
52
66
  def resolve(f)
53
67
  self.sub_message = sub_message.resolve(f)
@@ -55,7 +69,7 @@ module Ynl
55
69
  end
56
70
 
57
71
  def rbs_type
58
- 'untyped'
72
+ '::String'
59
73
  end
60
74
  end
61
75
 
@@ -96,7 +110,7 @@ module Ynl
96
110
  end
97
111
 
98
112
  def rbs_type
99
- 'untyped'
113
+ "::Array[#{sub_type.rbs_type}]"
100
114
  end
101
115
  end
102
116
  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.2.4
4
+ version: 0.3.0
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.2.4
18
+ version: 0.3.0
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.2.4
25
+ version: 0.3.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: yaml
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -37,7 +37,8 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
- description: Linux Netlink
40
+ description: ynl parses YAML Netlink specifications (YNL) and generates Netlink clients
41
+ for the interfaces they describe.
41
42
  email:
42
43
  - kasumi@rollingapple.net
43
44
  executables: []
@@ -70,7 +71,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
71
  requirements:
71
72
  - - ">="
72
73
  - !ruby/object:Gem::Version
73
- version: '3.1'
74
+ version: '3.4'
74
75
  required_rubygems_version: !ruby/object:Gem::Requirement
75
76
  requirements:
76
77
  - - ">="
@@ -79,5 +80,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
80
  requirements: []
80
81
  rubygems_version: 4.0.6
81
82
  specification_version: 4
82
- summary: Linux Netlink - core
83
+ summary: YAML Netlink specification tools for Ruby
83
84
  test_files: []