relaton-ietf 2.1.1 → 2.1.3
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/lib/relaton/ietf/data_fetcher.rb +171 -98
- data/lib/relaton/ietf/rfc/entry.rb +4 -3
- data/lib/relaton/ietf/version.rb +1 -1
- data/relaton-ietf.gemspec +2 -1
- metadata +17 -8
- data/grammars/basicdoc.rng +0 -2140
- data/grammars/biblio-standoc.rng +0 -268
- data/grammars/biblio.rng +0 -2125
- data/grammars/relaton-ietf-compile.rng +0 -11
- data/grammars/relaton-ietf.rng +0 -331
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 316a5b5786dc1730e23428a30445debf9cd52a11d32dd34511d992bbeceafec5
|
|
4
|
+
data.tar.gz: 0f94341cfcc4a18a1dc14a831c482564166d145342a093dbf8f5a65f43c6e334
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9072422ff3c903d155f89f95160636d83352f88379513fb1c17a2dec5acdd983dbbaf3f2dfe4a8d07180856fdee87a8dcdd56c2f3d8a8497de217233c6af248b
|
|
7
|
+
data.tar.gz: 9a8d31c7a865112f409bd1ab38a7390a8530b21090b98d104cf82df0b60748ae127f599b21898aca95c1ec7a56231ae1348e0ae9661159f4cba2ace6a1a61ab8
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "etc"
|
|
2
|
+
require "parallel"
|
|
1
3
|
require "relaton/core"
|
|
2
4
|
require_relative "../ietf"
|
|
3
5
|
require_relative "bibxml_parser"
|
|
@@ -41,107 +43,158 @@ module Relaton
|
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
#
|
|
44
|
-
# Fetches ietf-internet-drafts documents
|
|
46
|
+
# Fetches ietf-internet-drafts documents.
|
|
45
47
|
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
# Each work unit (one series, or one singleton XML) is processed
|
|
49
|
+
# end-to-end in a worker process: parse → link relations → serialize →
|
|
50
|
+
# write. Workers return Marshal-friendly index entries; the parent
|
|
51
|
+
# collects them and updates `Relaton::Index` and the duplicate-check set
|
|
52
|
+
# serially. Set `RELATON_IETF_PARALLEL_WORKERS=0` to force serial
|
|
53
|
+
# execution (useful for tests and debugging).
|
|
54
|
+
#
|
|
55
|
+
def fetch_ieft_internet_drafts
|
|
56
|
+
series_groups, singleton_paths = group_draft_paths
|
|
57
|
+
|
|
58
|
+
series_results = parallelize(series_groups.to_a) do |(series, paths_info)|
|
|
59
|
+
process_series(series, paths_info)
|
|
60
|
+
end.flatten(1)
|
|
61
|
+
|
|
62
|
+
singleton_results = parallelize(singleton_paths) do |path|
|
|
63
|
+
process_singleton(path)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
(series_results + singleton_results).compact.each { |r| record_index_entry(r) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# Run `block` once per item, in parallel worker processes when configured.
|
|
71
|
+
# `Parallel.map(items, in_processes: 0)` runs synchronously in the
|
|
72
|
+
# current process, which keeps tests deterministic and lets mocks work.
|
|
73
|
+
#
|
|
74
|
+
def parallelize(items, &block)
|
|
75
|
+
Parallel.map(items, in_processes: worker_count, &block)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def worker_count
|
|
79
|
+
ENV.fetch("RELATON_IETF_PARALLEL_WORKERS", Etc.nprocessors.to_s).to_i
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
#
|
|
83
|
+
# Filename-only scan: group versioned drafts by normalized series stem;
|
|
84
|
+
# everything else (non-versioned, non-`D.draft-`) goes to singletons.
|
|
85
|
+
# No XML parsing happens here — workers do that.
|
|
86
|
+
#
|
|
87
|
+
# @return [Array(Hash, Array<String>)]
|
|
88
|
+
# series_groups: { normalized_series => [{path, ver, ref}, ...] }
|
|
89
|
+
# singleton_paths: [path, ...]
|
|
90
|
+
#
|
|
91
|
+
def group_draft_paths
|
|
92
|
+
series_groups = {}
|
|
93
|
+
singleton_paths = []
|
|
94
|
+
Dir["bibxml-ids/*.xml"].each do |path|
|
|
95
|
+
file = File.basename(path, ".xml")
|
|
96
|
+
is_draft = file.include?("D.draft-")
|
|
97
|
+
ver = is_draft ? file[/(\d+)$/, 1] : nil
|
|
98
|
+
ref = file.sub(/^reference\.I-D\./, "").downcase
|
|
99
|
+
stem_match = is_draft && ver ? /^(draft-.+)-(\d{2})$/.match(ref) : nil
|
|
100
|
+
if stem_match
|
|
101
|
+
series = stem_match[1].gsub(/[.\s\/:-]+/, "-")
|
|
102
|
+
(series_groups[series] ||= []) << { path: path, ver: ver, ref: ref }
|
|
103
|
+
else
|
|
104
|
+
singleton_paths << path
|
|
58
105
|
end
|
|
59
|
-
save_doc bib
|
|
60
106
|
end
|
|
61
|
-
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
107
|
+
[series_groups, singleton_paths]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
#
|
|
111
|
+
# Worker: parse all files in a series, sort by version, append
|
|
112
|
+
# immediate-neighbor relations (skipped for bibxml), write each version
|
|
113
|
+
# and the un-versioned aggregator doc. Returns an array of index entries
|
|
114
|
+
# for the parent.
|
|
115
|
+
#
|
|
116
|
+
def process_series(series, paths_info)
|
|
117
|
+
sorted = paths_info.sort_by { |p| p[:ver].to_i }.filter_map do |p|
|
|
118
|
+
bib = BibXMLParser.parse(File.read(p[:path], encoding: "UTF-8"))
|
|
119
|
+
bib.version = [Bib::Version.new(draft: p[:ver])]
|
|
120
|
+
p.merge(bib: bib, source: bib.source)
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
Util.error "Error parsing #{p[:path]}: #{e.message}\n#{e.backtrace[0..5].join("\n")}"
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
link_neighbor_relations(sorted) if @format != "bibxml"
|
|
126
|
+
|
|
127
|
+
results = sorted.map { |entry| serialize_and_write(entry[:bib]) }
|
|
128
|
+
results << serialize_and_write(build_unversioned_doc(series, sorted)) if @format != "bibxml"
|
|
129
|
+
results.compact
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
#
|
|
133
|
+
# Worker: parse + serialize + write a single non-grouped XML.
|
|
134
|
+
#
|
|
135
|
+
def process_singleton(path)
|
|
136
|
+
file = File.basename(path, ".xml")
|
|
137
|
+
is_draft = file.include?("D.draft-")
|
|
138
|
+
ver = is_draft ? file[/(\d+)$/, 1] : nil
|
|
139
|
+
bib = BibXMLParser.parse(File.read(path, encoding: "UTF-8"))
|
|
140
|
+
bib.version = [Bib::Version.new(draft: ver)] if ver
|
|
141
|
+
serialize_and_write(bib)
|
|
142
|
+
rescue StandardError => e
|
|
143
|
+
Util.error "Error parsing #{path}: #{e.message}\n#{e.backtrace[0..5].join("\n")}"
|
|
144
|
+
nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
#
|
|
148
|
+
# Append immediate-neighbor `updates` / `updatedBy` relations in memory.
|
|
149
|
+
# Single-version series get no relations (no neighbors).
|
|
150
|
+
#
|
|
151
|
+
def link_neighbor_relations(sorted)
|
|
152
|
+
sorted.each_with_index do |entry, i|
|
|
153
|
+
if i.positive?
|
|
154
|
+
prev = sorted[i - 1]
|
|
155
|
+
entry[:bib].relation << version_relation({ ref: prev[:ref], source: prev[:source] }, "updates")
|
|
156
|
+
end
|
|
157
|
+
if i < sorted.size - 1
|
|
158
|
+
nxt = sorted[i + 1]
|
|
159
|
+
entry[:bib].relation << version_relation({ ref: nxt[:ref], source: nxt[:source] }, "updatedBy")
|
|
88
160
|
end
|
|
89
161
|
end
|
|
90
162
|
end
|
|
91
163
|
|
|
92
164
|
#
|
|
93
|
-
#
|
|
165
|
+
# Build (but do not write) the un-versioned series aggregator doc with
|
|
166
|
+
# `includes` relations to every version. Uses the latest version's
|
|
167
|
+
# title/abstract from memory.
|
|
94
168
|
#
|
|
95
|
-
# @
|
|
96
|
-
# @param [Array<String>] versions list of versions
|
|
169
|
+
# @return [Relaton::Ietf::ItemData, nil]
|
|
97
170
|
#
|
|
98
|
-
def
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return
|
|
171
|
+
def build_unversioned_doc(series, sorted)
|
|
172
|
+
if sorted.empty?
|
|
173
|
+
Util.warn "No versions found for #{series}"
|
|
174
|
+
return nil
|
|
103
175
|
end
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
bib = ItemData.new(
|
|
111
|
-
title: last_v.title, abstract: last_v.abstract, formattedref: Bib::Formattedref.new(content: ref),
|
|
176
|
+
|
|
177
|
+
last_v = sorted.last[:bib]
|
|
178
|
+
docid = Bib::Docidentifier.new(type: "Internet-Draft", content: series, primary: true)
|
|
179
|
+
rel = sorted.map { |e| version_relation({ ref: e[:ref], source: e[:source] }, "includes") }
|
|
180
|
+
ItemData.new(
|
|
181
|
+
title: last_v.title, abstract: last_v.abstract, formattedref: Bib::Formattedref.new(content: series),
|
|
112
182
|
docidentifier: [docid], relation: rel
|
|
113
183
|
)
|
|
114
|
-
save_doc bib
|
|
115
184
|
end
|
|
116
185
|
|
|
117
186
|
#
|
|
118
187
|
# Create bibitem relation
|
|
119
188
|
#
|
|
120
|
-
# @param [
|
|
189
|
+
# @param [Hash] ver version reference, { ref:, source: }
|
|
121
190
|
# @param [String] type relation type
|
|
122
191
|
#
|
|
123
|
-
# @return [Relaton::
|
|
192
|
+
# @return [Relaton::Ietf::Relation] relation
|
|
124
193
|
#
|
|
125
194
|
def version_relation(ver, type)
|
|
126
195
|
docid = Bib::Docidentifier.new(type: "Internet-Draft", content: ver[:ref], primary: true)
|
|
127
196
|
bibitem = ItemData.new(formattedref: Bib::Formattedref.new(content: ver[:ref]), docidentifier: [docid], source: ver[:source])
|
|
128
|
-
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
#
|
|
132
|
-
# Redad saved documents
|
|
133
|
-
#
|
|
134
|
-
# @param [String] file path to file
|
|
135
|
-
#
|
|
136
|
-
# @return [Relaton::Ietf::ItemData] bibliographic item
|
|
137
|
-
#
|
|
138
|
-
def read_doc(file)
|
|
139
|
-
doc = File.read(file, encoding: "UTF-8")
|
|
140
|
-
case @format
|
|
141
|
-
when "xml" then Item.from_xml(doc)
|
|
142
|
-
when "yaml" then Item.from_yaml(doc)
|
|
143
|
-
else BibXMLParser.parse(doc)
|
|
144
|
-
end
|
|
197
|
+
Relaton::Ietf::Relation.new(type: type, bibitem: bibitem)
|
|
145
198
|
end
|
|
146
199
|
|
|
147
200
|
#
|
|
@@ -172,38 +225,58 @@ module Relaton
|
|
|
172
225
|
end
|
|
173
226
|
|
|
174
227
|
#
|
|
175
|
-
# Save document to file
|
|
228
|
+
# Save document to file (sequential path: serialize, write, index).
|
|
229
|
+
# Used by the rfcsubseries / rfc-entries fetchers; the I-D fetcher splits
|
|
230
|
+
# this into worker-safe `serialize_and_write` plus parent-only
|
|
231
|
+
# `record_index_entry` so the index is touched only in the main process.
|
|
176
232
|
#
|
|
177
|
-
# @param [Relaton::Ietf::Rfc::Entry, nil]
|
|
233
|
+
# @param [Relaton::Ietf::Rfc::Entry, nil] entry
|
|
178
234
|
# @param [Boolean] check_duplicate check for duplicate
|
|
179
235
|
#
|
|
180
|
-
def save_doc(entry, check_duplicate: true)
|
|
181
|
-
|
|
236
|
+
def save_doc(entry, check_duplicate: true)
|
|
237
|
+
result = serialize_and_write(entry)
|
|
238
|
+
record_index_entry(result, check_duplicate: check_duplicate) if result
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
#
|
|
242
|
+
# Worker-safe: serialize, compute output filename, write to disk, return
|
|
243
|
+
# a Marshal-friendly hash with the docid+file pair the parent needs to
|
|
244
|
+
# update `Relaton::Index` and `@files`. Does NOT touch instance state
|
|
245
|
+
# that has to stay consistent across workers (`@files`, the index).
|
|
246
|
+
#
|
|
247
|
+
# @param [#to_yaml, #to_xml, #to_rfcxml, nil] entry
|
|
248
|
+
# @return [Hash, nil]
|
|
249
|
+
#
|
|
250
|
+
def serialize_and_write(entry) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
|
251
|
+
return nil unless entry
|
|
182
252
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
253
|
+
content = case @format
|
|
254
|
+
when "xml" then entry.to_xml(bibdata: true)
|
|
255
|
+
when "yaml" then entry.to_yaml
|
|
256
|
+
when "bibxml" then entry.to_rfcxml
|
|
257
|
+
else entry.send("to_#{@format}")
|
|
258
|
+
end
|
|
189
259
|
id = if entry.respond_to?(:docidentifier)
|
|
190
260
|
entry.docidentifier.detect { |i| i.type == "Internet-Draft" && i.primary }&.content
|
|
191
261
|
end
|
|
192
262
|
id ||= entry.docnumber || entry.formattedref.content
|
|
193
263
|
file = output_file(id)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
@files << file
|
|
198
|
-
end
|
|
199
|
-
File.write file, c, encoding: "UTF-8"
|
|
200
|
-
add_to_index entry, file
|
|
264
|
+
File.write file, content, encoding: "UTF-8"
|
|
265
|
+
primary = entry.docidentifier.detect(&:primary) || entry.docidentifier.first
|
|
266
|
+
{ docnumber: entry.docnumber, file: file, index_id: primary.content }
|
|
201
267
|
end
|
|
202
268
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
269
|
+
#
|
|
270
|
+
# Parent-only: dedupe-check `@files` and update `Relaton::Index`. Called
|
|
271
|
+
# serially after workers return so index updates are race-free.
|
|
272
|
+
#
|
|
273
|
+
def record_index_entry(result, check_duplicate: true)
|
|
274
|
+
if check_duplicate && @files.include?(result[:file])
|
|
275
|
+
Util.warn "File #{result[:file]} already exists. Document: #{result[:docnumber]}"
|
|
276
|
+
elsif check_duplicate
|
|
277
|
+
@files << result[:file]
|
|
278
|
+
end
|
|
279
|
+
index.add_or_update result[:index_id], result[:file]
|
|
207
280
|
end
|
|
208
281
|
|
|
209
282
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "cgi"
|
|
3
4
|
require_relative "rfc_index_namespace"
|
|
4
5
|
require_relative "is_also"
|
|
5
6
|
require_relative "author"
|
|
@@ -212,7 +213,7 @@ module Relaton
|
|
|
212
213
|
is_also.doc_id.map do |ref|
|
|
213
214
|
rfc_entry = rfc_index&.[](ref)
|
|
214
215
|
bibitem = rfc_entry ? rfc_entry.to_rfc_item(wg_names: wg_names) : build_minimal_bibitem(ref)
|
|
215
|
-
|
|
216
|
+
Relaton::Ietf::Relation.new(type: "includes", bibitem: bibitem)
|
|
216
217
|
end.compact
|
|
217
218
|
end
|
|
218
219
|
|
|
@@ -317,7 +318,7 @@ module Relaton
|
|
|
317
318
|
def build_rfc_abstract
|
|
318
319
|
return [] unless abstract&.p&.any?
|
|
319
320
|
|
|
320
|
-
content = abstract.p.map { |para| "<p>#{para.strip}</p>" }.join
|
|
321
|
+
content = abstract.p.map { |para| "<p>#{CGI.escapeHTML(para.strip)}</p>" }.join
|
|
321
322
|
[Bib::Abstract.new(content: content, language: "en", script: "Latn")]
|
|
322
323
|
end
|
|
323
324
|
|
|
@@ -339,7 +340,7 @@ module Relaton
|
|
|
339
340
|
def build_rfc_doc_relation(ref, type)
|
|
340
341
|
docid = Bib::Docidentifier.new(type: "IETF", content: ref, primary: true)
|
|
341
342
|
bibitem = ItemData.new(formattedref: Bib::Formattedref.new(content: ref), docidentifier: [docid])
|
|
342
|
-
|
|
343
|
+
Relaton::Ietf::Relation.new(type: type, bibitem: bibitem)
|
|
343
344
|
end
|
|
344
345
|
|
|
345
346
|
def build_rfc_status
|
data/lib/relaton/ietf/version.rb
CHANGED
data/relaton-ietf.gemspec
CHANGED
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
|
18
18
|
|
|
19
19
|
Formerly known as rfcbib.
|
|
20
20
|
DESCRIPTION
|
|
21
|
-
spec.homepage = "https://github.com/
|
|
21
|
+
spec.homepage = "https://github.com/relaton/relaton-ietf"
|
|
22
22
|
spec.license = "BSD-2-Clause"
|
|
23
23
|
|
|
24
24
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
|
|
|
30
30
|
spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0")
|
|
31
31
|
|
|
32
32
|
spec.add_dependency "base64"
|
|
33
|
+
spec.add_dependency "parallel", "~> 1.26"
|
|
33
34
|
spec.add_dependency "relaton-bib", "~> 2.1.0"
|
|
34
35
|
spec.add_dependency "relaton-core", "~> 0.0.13"
|
|
35
36
|
spec.add_dependency "relaton-index", "~> 0.2.3"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: relaton-ietf
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: parallel
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.26'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.26'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: relaton-bib
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -91,11 +105,6 @@ files:
|
|
|
91
105
|
- bin/console
|
|
92
106
|
- bin/rspec
|
|
93
107
|
- bin/setup
|
|
94
|
-
- grammars/basicdoc.rng
|
|
95
|
-
- grammars/biblio-standoc.rng
|
|
96
|
-
- grammars/biblio.rng
|
|
97
|
-
- grammars/relaton-ietf-compile.rng
|
|
98
|
-
- grammars/relaton-ietf.rng
|
|
99
108
|
- lib/relaton/ietf.rb
|
|
100
109
|
- lib/relaton/ietf/bibdata.rb
|
|
101
110
|
- lib/relaton/ietf/bibitem.rb
|
|
@@ -124,7 +133,7 @@ files:
|
|
|
124
133
|
- lib/relaton/ietf/version.rb
|
|
125
134
|
- lib/relaton/ietf/wg_name_resolver.rb
|
|
126
135
|
- relaton-ietf.gemspec
|
|
127
|
-
homepage: https://github.com/
|
|
136
|
+
homepage: https://github.com/relaton/relaton-ietf
|
|
128
137
|
licenses:
|
|
129
138
|
- BSD-2-Clause
|
|
130
139
|
metadata: {}
|