kramdown-rfc2629 1.5.26 → 1.6.3

Sign up to get free protection for your applications and to get access to all the features.
data/bin/kramdown-rfc2629 CHANGED
@@ -1,539 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  # -*- coding: utf-8 -*-
3
- require 'kramdown-rfc2629'
4
- require 'kramdown-rfc/parameterset'
5
- require 'kramdown-rfc/refxml'
6
- require 'kramdown-rfc/rfc8792'
7
- require 'yaml'
8
- require 'kramdown-rfc/erb'
9
- require 'date'
3
+ require 'kramdown-rfc/command'
10
4
 
11
- # try to get this from gemspec.
12
- KDRFC_VERSION=Gem.loaded_specs["kramdown-rfc2629"].version rescue "unknown-version"
13
-
14
- Encoding.default_external = "UTF-8" # wake up, smell the coffee
15
-
16
- # Note that this doesn't attempt to handle HT characters
17
- def remove_indentation(s)
18
- l = s.lines
19
- indent = l.grep(/\S/).map {|l| l[/^\s*/].size}.min
20
- l.map {|li| li.sub(/^ {0,#{indent}}/, "")}.join
21
- end
22
-
23
- def add_quote(s)
24
- l = s.lines
25
- l.map {|li| "> #{li}"}.join
26
- end
27
-
28
- def process_chunk(s, nested, dedent, fold, quote)
29
- process_includes(s) if nested
30
- s = remove_indentation(s) if dedent
31
- s = fold8792_1(s, *fold) if fold
32
- s = add_quote(s) if quote
33
- s
34
- end
35
-
36
- def process_includes(input)
37
- input.gsub!(/^\{::include((?:-[a-z0-9]+)*)\s+(.*?)\}/) {
38
- include_flags = $1
39
- fn = [$2]
40
- chunks = false
41
- nested = false
42
- dedent = false
43
- fold = false
44
- quote = false
45
- include_flags.split("-") do |flag|
46
- case flag
47
- when ""
48
- when "nested"
49
- nested = true
50
- when "quote"
51
- quote = true
52
- when "dedent"
53
- dedent = true
54
- when /\Afold(\d*)(left(\d*))?(dry)?\z/
55
- fold = [$1.to_i, # col 0 for ''
56
- ($3.to_i if $2), # left 0 for '', nil if no "left"
57
- $4] # dry
58
- when "all", "last"
59
- fn = fn.flat_map{|n| Dir[n]}
60
- fn = [fn.last] if flag == "last"
61
- chunks = fn.map{ |f|
62
- ret = process_chunk(File.read(f), nested, dedent, fold, quote)
63
- nested = false; dedent = false; fold = false; quote = false
64
- ret
65
- }
66
- else
67
- warn "** unknown include flag #{flag}"
68
- end
69
- end
70
- chunks = fn.map{|f| File.read(f)} unless chunks # no all/last
71
- chunks = chunks.map {|ch| process_chunk(ch, nested, dedent, fold, quote)}
72
- chunks.join.chomp
73
- }
74
- end
75
-
76
-
77
- def boilerplate(key)
78
- case key.downcase
79
- when /\Abcp14(info)?(\+)?(-tagged)?\z/i
80
- ret = ''
81
- if $1
82
- ret << <<RFC8174ise
83
- Although this document is not an IETF Standards Track publication, it
84
- adopts the conventions for normative language to provide clarity of
85
- instructions to the implementer.
86
- RFC8174ise
87
- end
88
- ret << <<RFC8174
89
- The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
90
- NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED",
91
- "MAY", and "OPTIONAL" in this document are to be interpreted as
92
- described in BCP 14 {{!RFC2119}} {{!RFC8174}} when, and only when, they
93
- appear in all capitals, as shown here.
94
- RFC8174
95
- if $2
96
- ret << <<PLUS
97
- These words may also appear in this document in
98
- lower case as plain English words, absent their normative meanings.
99
- PLUS
100
- end
101
- if $3
102
- if $options.v3
103
- ret << <<TAGGED
104
-
105
- *[MUST]: <bcp14>
106
- *[MUST NOT]: <bcp14>
107
- *[REQUIRED]: <bcp14>
108
- *[SHALL]: <bcp14>
109
- *[SHALL NOT]: <bcp14>
110
- *[SHOULD]: <bcp14>
111
- *[SHOULD NOT]: <bcp14>
112
- *[RECOMMENDED]: <bcp14>
113
- *[NOT RECOMMENDED]: <bcp14>
114
- *[MAY]: <bcp14>
115
- *[OPTIONAL]: <bcp14>
116
- TAGGED
117
- else
118
- warn "** need --v3 to tag bcp14"
119
- end
120
- end
121
- ret
122
- else
123
- warn "** Unknwon boilerplate key: #{key}"
124
- "{::boilerplate #{key}}"
125
- end
126
- end
127
-
128
- def do_the_tls_dance
129
- begin
130
- require 'openssl'
131
- File.open(OpenSSL::X509::DEFAULT_CERT_FILE) do end
132
- # This guards against having an unreadable cert file (yes, that appears to happen a lot).
133
- rescue
134
- if Dir[File.join(OpenSSL::X509::DEFAULT_CERT_DIR, "*.pem")].empty?
135
- # This guards against having no certs at all, not against missing the right one for IETF.
136
- # Oh well.
137
- warn "** Configuration problem with OpenSSL certificate store."
138
- warn "** You may want to examine #{OpenSSL::X509::DEFAULT_CERT_FILE}"
139
- warn "** and #{OpenSSL::X509::DEFAULT_CERT_DIR}."
140
- warn "** Activating suboptimal workaround."
141
- warn "** Occasionally run `certified-update` to maintain that workaround."
142
- require 'certified'
143
- end
144
- end
145
- end
146
-
147
- RE_NL = /(?:\n|\r|\r\n)/
148
- RE_SECTION = /---(?: +(\w+)(-?))?\s*#{RE_NL}(.*?#{RE_NL})(?=---(?:\s+\w+-?)?\s*#{RE_NL}|\Z)/m
149
-
150
- NMDTAGS = ["{:/nomarkdown}\n\n", "\n\n{::nomarkdown}\n"]
151
-
152
- NORMINFORM = { "!" => :normative, "?" => :informative }
153
-
154
- def yaml_load(input, *args)
155
- if YAML.respond_to?(:safe_load)
156
- begin
157
- YAML.safe_load(input, *args)
158
- rescue ArgumentError
159
- YAML.safe_load(input, permitted_classes: args[0], permitted_symbols: args[1], aliases: args[2])
160
- end
161
- else
162
- YAML.load(input)
163
- end
164
- end
165
-
166
- def process_kramdown_options(coding_override = nil,
167
- smart_quotes = nil, typographic_symbols = nil,
168
- header_kramdown_options = nil)
169
-
170
- ascii_target = coding_override && coding_override =~ /ascii/
171
- suppress_typography = ascii_target || $options.v3
172
- entity_output = ascii_target ? :numeric : :as_char;
173
-
174
- options = {input: 'RFC2629Kramdown', entity_output: entity_output, link_defs: {}}
175
-
176
- if smart_quotes.nil? && suppress_typography
177
- smart_quotes = false
178
- end
179
- if smart_quotes == false
180
- smart_quotes = ["'".ord, "'".ord, '"'.ord, '"'.ord]
181
- end
182
- case smart_quotes
183
- when Array
184
- options[:smart_quotes] = smart_quotes
185
- when nil, true
186
- # nothin
187
- else
188
- warn "*** Can't deal with smart_quotes value #{smart_quotes.inspect}"
189
- end
190
-
191
- if typographic_symbols.nil? && suppress_typography
192
- typographic_symbols = false
193
- end
194
- if typographic_symbols == false
195
- typographic_symbols = Hash[::Kramdown::Parser::Kramdown::TYPOGRAPHIC_SYMS.map { |k, v|
196
- if Symbol === v
197
- [v.intern, k]
198
- end
199
- }.compact]
200
- end
201
- # warn [:TYPOGRAPHIC_SYMBOLS, typographic_symbols].to_yaml
202
- case typographic_symbols
203
- when Hash
204
- options[:typographic_symbols] = typographic_symbols
205
- when nil, true
206
- # nothin
207
- else
208
- warn "*** Can't deal with typographic_symbols value #{typographic_symbols.inspect}"
209
- end
210
-
211
- if header_kramdown_options
212
- options.merge! header_kramdown_options
213
- end
214
-
215
- $global_markdown_options = options # For nested calls in bibref annotation processing and xref text
216
-
217
- options
218
- end
219
-
220
- XREF_SECTIONS_RE = ::Kramdown::Parser::RFC2629Kramdown::SECTIONS_RE
221
- XSR_PREFIX = "#{XREF_SECTIONS_RE} of "
222
- XSR_SUFFIX = ", (#{XREF_SECTIONS_RE})| \\((#{XREF_SECTIONS_RE})\\)"
223
- XREF_TXT = ::Kramdown::Parser::RFC2629Kramdown::XREF_TXT
224
- XREF_TXT_SUFFIX = " \\(#{XREF_TXT}\\)"
225
-
226
- def spacify_re(s)
227
- s.gsub(' ', '[\u00A0\s]+')
228
- end
229
-
230
- def xml_from_sections(input)
231
-
232
- unless ENV["KRAMDOWN_NO_SOURCE"]
233
- require 'kramdown-rfc/gzip-clone'
234
- require 'base64'
235
- compressed_input = Gzip.compress(input)
236
- $source = Base64.encode64(compressed_input)
237
- end
238
-
239
- sections = input.scan(RE_SECTION)
240
- # resulting in an array; each section is [section-label, nomarkdown-flag, section-text]
241
-
242
- # the first section is a YAML with front matter parameters (don't put a label here)
243
- # We put back the "---" plus gratuitous blank lines to hack the line number in errors
244
- yaml_in = input[/---\s*/] << sections.shift[2]
245
- ps = KramdownRFC::ParameterSet.new(yaml_load(yaml_in, [Date], [], true))
246
-
247
- coding_override = ps.has(:coding)
248
- smart_quotes = ps[:smart_quotes]
249
- typographic_symbols = ps[:typographic_symbols]
250
- header_kramdown_options = ps[:kramdown_options]
251
-
252
- kramdown_options = process_kramdown_options(coding_override,
253
- smart_quotes, typographic_symbols,
254
- header_kramdown_options)
255
-
256
- # all the other sections are put in a Hash, possibly concatenated from parts there
257
- sechash = Hash.new{ |h,k| h[k] = ""}
258
- snames = [] # a stack of section names
259
- sections.each do |sname, nmdflag, text|
260
- # warn [:SNAME, sname, nmdflag, text[0..10]].inspect
261
- nmdin, nmdout = {
262
- "-" => ["", ""], # stay in nomarkdown
263
- "" => NMDTAGS, # pop out temporarily
264
- }[nmdflag || ""]
265
- if sname
266
- snames << sname # "--- label" -> push label (now current)
267
- else
268
- snames.pop # just "---" -> pop label (previous now current)
269
- end
270
- sechash[snames.last] << "#{nmdin}#{text}#{nmdout}"
271
- end
272
-
273
- ref_replacements = { }
274
- anchor_to_bibref = { }
275
-
276
- [:ref, :normative, :informative].each do |sn|
277
- if refs = ps.has(sn)
278
- warn "*** bad section #{sn}: #{refs.inspect}" unless refs.respond_to? :each
279
- refs.each do |k, v|
280
- if v.respond_to? :to_str
281
- if bibtagsys(v) # enable "foo: RFC4711" as a custom anchor definition
282
- anchor_to_bibref[k] = v.to_str
283
- end
284
- ref_replacements[v.to_str] = k
285
- end
286
- if Hash === v
287
- if aliasname = v.delete("-")
288
- ref_replacements[aliasname] = k
289
- end
290
- if bibref = v.delete("=")
291
- anchor_to_bibref[k] = bibref
292
- end
293
- end
294
- end
295
- end
296
- end
297
- open_refs = ps[:ref] || { } # consumed
298
-
299
- norm_ref = { }
300
-
301
- # convenience replacement of {{-coap}} with {{I-D.ietf-core-coap}}
302
- # collect normative/informative tagging {{!RFC2119}} {{?RFC4711}}
303
- sechash.each do |k, v|
304
- next if k == "fluff"
305
- v.gsub!(/{{(#{
306
- spacify_re(XSR_PREFIX)
307
- })?(?:([?!])(-)?|(-))([\w._\-]+)(?:=([\w.\/_\-]+))?(#{
308
- XREF_TXT_SUFFIX
309
- })?(#{
310
- spacify_re(XSR_SUFFIX)
311
- })?}}/) do |match|
312
- xsr_prefix = $1
313
- norminform = $2
314
- replacing = $3 || $4
315
- word = $5
316
- bibref = $6
317
- xrt_suffix = $7
318
- xsr_suffix = $8
319
- if replacing
320
- if new = ref_replacements[word]
321
- word = new
322
- else
323
- warn "*** no alias replacement for {{-#{word}}}"
324
- word = "-#{word}"
325
- end
326
- end # now, word is the anchor
327
- if bibref
328
- if old = anchor_to_bibref[word]
329
- if bibref != old
330
- warn "*** conflicting definitions for xref #{word}: #{old} != #{bibref}"
331
- end
332
- else
333
- anchor_to_bibref[word] = bibref
334
- end
335
- end
336
-
337
- # things can be normative in one place and informative in another -> normative
338
- # collect norm/inform above and assign it by priority here
339
- if norminform
340
- norm_ref[word] ||= norminform == '!' # one normative ref is enough
341
- end
342
- "{{#{xsr_prefix}#{word}#{xrt_suffix}#{xsr_suffix}}}"
343
- end
344
- end
345
-
346
- [:normative, :informative].each do |k|
347
- ps.rest[k.to_s] ||= { }
348
- end
349
-
350
- norm_ref.each do |k, v|
351
- # could check bibtagsys here: needed if open_refs is nil or string
352
- target = ps.has(v ? :normative : :informative)
353
- warn "*** overwriting #{k}" if target.has_key?(k)
354
- target[k] = open_refs[k] # add reference to normative/informative
355
- end
356
- # note that unused items from ref are considered OK, therefore no check for that here
357
-
358
- # also should allow norm/inform check of other references
359
- # {{?coap}} vs. {{!coap}} vs. {{-coap}} (undecided)
360
- # or {{?-coap}} vs. {{!-coap}} vs. {{-coap}} (undecided)
361
- # could require all references to be decided by a global flag
362
- overlap = [:normative, :informative].map { |s| (ps.has(s) || { }).keys }.reduce(:&)
363
- unless overlap.empty?
364
- warn "*** #{overlap.join(', ')}: both normative and informative"
365
- end
366
-
367
- stand_alone = ps[:stand_alone]
368
-
369
- [:normative, :informative].each do |sn|
370
- if refs = ps[sn]
371
- refs.each do |k, v|
372
- href = ::Kramdown::Parser::RFC2629Kramdown.idref_cleanup(k)
373
- kramdown_options[:link_defs][k] = ["##{href}", nil] # allow [RFC2119] in addition to {{RFC2119}}
374
-
375
- bibref = anchor_to_bibref[k] || k
376
- bts, url = bibtagsys(bibref, k, stand_alone)
377
- if bts && (!v || v == {} || v.respond_to?(:to_str))
378
- if stand_alone
379
- a = %{{: anchor="#{k}"}}
380
- sechash[sn.to_s] << %{\n#{NMDTAGS[0]}\n![:include:](#{bts})#{a}\n#{NMDTAGS[1]}\n}
381
- else
382
- bts.gsub!('/', '_')
383
- (ps.rest["bibxml"] ||= []) << [bts, url]
384
- sechash[sn.to_s] << %{&#{bts};\n} # ???
385
- end
386
- else
387
- unless v && Hash === v
388
- warn "*** don't know how to expand ref #{k}"
389
- next
390
- end
391
- if bts && !v.delete("override")
392
- warn "*** warning: explicit settings completely override canned bibxml in reference #{k}"
393
- end
394
- sechash[sn.to_s] << KramdownRFC::ref_to_xml(href, v)
395
- end
396
- end
397
- end
398
- end
399
-
400
- erbfilename = File.expand_path '../../data/kramdown-rfc2629.erb', __FILE__
401
- erbfile = File.read(erbfilename, coding: "UTF-8")
402
- erb = ERB.trim_new(erbfile, '-')
403
- # remove redundant nomarkdown pop outs/pop ins as they confuse kramdown
404
- input = erb.result(binding).gsub(%r"{::nomarkdown}\s*{:/nomarkdown}"m, "")
405
- ps.warn_if_leftovers
406
- sechash.delete("fluff") # fluff is a "commented out" section
407
- if !sechash.empty? # any sections unused by the ERb file?
408
- warn "*** sections left #{sechash.keys.inspect}!"
409
- end
410
-
411
- [input, kramdown_options, coding_override]
412
- end
413
-
414
- XML_RESOURCE_ORG_PREFIX = Kramdown::Converter::Rfc2629::XML_RESOURCE_ORG_PREFIX
415
-
416
- # return XML entity name, url, rewrite_anchor flag
417
- def bibtagsys(bib, anchor=nil, stand_alone=true)
418
- if bib =~ /\Arfc(\d+)/i
419
- rfc4d = "%04d" % $1.to_i
420
- [bib.upcase,
421
- "#{XML_RESOURCE_ORG_PREFIX}/bibxml/reference.RFC.#{rfc4d}.xml"]
422
- elsif $options.v3 && bib =~ /\A(bcp|std)(\d+)/i
423
- n4d = "%04d" % $2.to_i
424
- [bib.upcase,
425
- "#{XML_RESOURCE_ORG_PREFIX}/bibxml-rfcsubseries-new/reference.#{$1.upcase}.#{n4d}.xml"]
426
- elsif bib =~ /\A([-A-Z0-9]+)\./ &&
427
- (xro = Kramdown::Converter::Rfc2629::XML_RESOURCE_ORG_MAP[$1])
428
- dir, _ttl, rewrite_anchor = xro
429
- bib1 = ::Kramdown::Parser::RFC2629Kramdown.idref_cleanup(bib)
430
- if anchor && bib1 != anchor
431
- if rewrite_anchor
432
- a = %{?anchor=#{anchor}}
433
- else
434
- if !stand_alone
435
- warn "*** selecting a custom anchor '#{anchor}' for '#{bib1}' requires stand_alone mode"
436
- warn " the output will need manual editing to correct this"
437
- end
438
- end
439
- end
440
- [bib1,
441
- "#{XML_RESOURCE_ORG_PREFIX}/#{dir}/reference.#{bib}.xml#{a}"]
442
- end
443
- end
444
-
445
- def read_encodings
446
- encfilename = File.expand_path '../../data/encoding-fallbacks.txt', __FILE__
447
- encfile = File.read(encfilename, coding: "UTF-8")
448
- Hash[encfile.lines.map{|l|
449
- l.chomp!;
450
- x, s = l.split(" ", 2)
451
- [x.hex.chr(Encoding::UTF_8), s || " "]}]
452
- end
453
-
454
- FALLBACK = read_encodings
455
-
456
- def expand_tabs(s, tab_stops = 8)
457
- s.gsub(/([^\t\n]*)\t/) do
458
- $1 + " " * (tab_stops - ($1.size % tab_stops))
459
- end
460
- end
461
-
462
-
463
- require 'optparse'
464
- require 'ostruct'
465
-
466
- $options = OpenStruct.new
467
- op = OptionParser.new do |opts|
468
- opts.banner = <<BANNER
469
- Usage: kramdown-rfc2629 [options] file.md|file.mkd > file.xml
470
- Version: #{KDRFC_VERSION}
471
- BANNER
472
- opts.on("-V", "--version", "Show version and exit") do |v|
473
- puts "kramdown-rfc2629 #{KDRFC_VERSION}"
474
- exit
475
- end
476
- opts.on("-H", "--help", "Show option summary and exit") do |v|
477
- puts opts
478
- exit
479
- end
480
- opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
481
- $options.verbose = v
482
- end
483
- opts.on("-3", "--[no-]v3", "Use RFCXML v3 processing rules") do |v|
484
- $options.v3 = v
485
- end
486
- end
487
- op.parse!
488
-
489
- if $options.verbose && $options.v3
490
- warn "*** not much RFCXMLv3 stuff implemented yet"
491
- end
492
-
493
- input = ARGF.read
494
- if input[0] == "\uFEFF"
495
- warn "*** There is a leading byte order mark. Ignored."
496
- input[0..0] = ''
497
- end
498
- if input[-1] != "\n"
499
- # warn "*** added missing newline at end"
500
- input << "\n" # fix #26
501
- end
502
- process_includes(input) unless ENV["KRAMDOWN_SAFE"]
503
- input.gsub!(/^\{::boilerplate\s+(.*?)\}/) {
504
- boilerplate($1)
505
- }
506
- if input =~ /[\t]/
507
- warn "*** Input contains HT (\"tab\") characters. Undefined behavior will ensue."
508
- input = expand_tabs(input)
509
- end
510
-
511
- if input =~ /\A---/ # this is a sectionized file
512
- do_the_tls_dance unless ENV["KRAMDOWN_DONT_VERIFY_HTTPS"]
513
- input, options, coding_override = xml_from_sections(input)
514
- else
515
- options = process_kramdown_options # all default
516
- end
517
- if input =~ /\A<\?xml/ # if this is a whole XML file, protect it
518
- input = "{::nomarkdown}\n#{input}\n{:/nomarkdown}\n"
519
- end
520
-
521
- if coding_override
522
- input = input.encode(Encoding.find(coding_override), fallback: FALLBACK)
523
- end
524
-
525
- # 1.4.17: because of UTF-8 bibxml files, kramdown always needs to see UTF-8 (!)
526
- if input.encoding != Encoding::UTF_8
527
- input = input.encode(Encoding::UTF_8)
528
- end
529
-
530
- # warn "options: #{options.inspect}"
531
- doc = Kramdown::Document.new(input, options)
532
- $stderr.puts doc.warnings.to_yaml unless doc.warnings.empty?
533
- output = doc.to_rfc2629
534
-
535
- if coding_override
536
- output = output.encode(Encoding.find(coding_override), fallback: FALLBACK)
537
- end
538
-
539
- puts output
@@ -169,6 +169,10 @@
169
169
 
170
170
  <back>
171
171
 
172
+ <% displayref.each do |k, v| -%>
173
+ <displayreference target="<%=k%>" to="<%=v%>"/>
174
+ <% end -%>
175
+
172
176
  <% if sh = sechash.delete("normative") -%>
173
177
  <references title='Normative References'>
174
178
 
@@ -1,16 +1,16 @@
1
1
  spec = Gem::Specification.new do |s|
2
2
  s.name = 'kramdown-rfc2629'
3
- s.version = '1.5.26'
4
- s.summary = "Kramdown extension for generating RFC 7749 XML."
5
- s.description = %{An RFC7749 (XML2RFC) generating backend for Thomas Leitner's
3
+ s.version = '1.6.3'
4
+ s.summary = "Kramdown extension for generating RFCXML (RFC 799x)."
5
+ s.description = %{An RFCXML (RFC 799x) generating backend for Thomas Leitner's
6
6
  "kramdown" markdown parser. Mostly useful for RFC writers.}
7
7
  s.add_dependency('kramdown', '~> 2.3.0')
8
8
  s.add_dependency('kramdown-parser-gfm', '~> 1.1')
9
9
  s.add_dependency('certified', '~> 1.0')
10
10
  s.add_dependency('json_pure', '~> 2.0')
11
- s.files = Dir['lib/**/*.rb'] + %w(README.md LICENSE kramdown-rfc2629.gemspec bin/kdrfc bin/kramdown-rfc2629 bin/doilit bin/kramdown-rfc-extract-markdown data/kramdown-rfc2629.erb data/encoding-fallbacks.txt data/math.json bin/kramdown-rfc-cache-subseries-bibxml bin/de-gfm)
11
+ s.files = Dir['lib/**/*.rb'] + %w(README.md LICENSE kramdown-rfc2629.gemspec bin/kdrfc bin/kramdown-rfc bin/kramdown-rfc2629 bin/doilit bin/kramdown-rfc-extract-markdown data/kramdown-rfc2629.erb data/encoding-fallbacks.txt data/math.json bin/kramdown-rfc-cache-subseries-bibxml bin/de-gfm)
12
12
  s.require_path = 'lib'
13
- s.executables = ['kramdown-rfc2629', 'doilit', 'kramdown-rfc-extract-markdown',
13
+ s.executables = ['kramdown-rfc', 'kramdown-rfc2629', 'doilit', 'kramdown-rfc-extract-markdown',
14
14
  'kdrfc', 'kramdown-rfc-cache-i-d-bibxml',
15
15
  'kramdown-rfc-cache-subseries-bibxml', 'de-gfm']
16
16
  s.required_ruby_version = '>= 2.3.0'