kramdown-rfc2629 1.5.25 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,572 @@
1
+ #!/usr/bin/env ruby
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'
10
+
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
+ ($options.v3_used ||= []) << "** need --v3 to tag bcp14"
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
+ end
118
+ ret
119
+ else
120
+ warn "** Unknwon boilerplate key: #{key}"
121
+ "{::boilerplate #{key}}"
122
+ end
123
+ end
124
+
125
+ def do_the_tls_dance
126
+ begin
127
+ require 'openssl'
128
+ File.open(OpenSSL::X509::DEFAULT_CERT_FILE) do end
129
+ # This guards against having an unreadable cert file (yes, that appears to happen a lot).
130
+ rescue
131
+ if Dir[File.join(OpenSSL::X509::DEFAULT_CERT_DIR, "*.pem")].empty?
132
+ # This guards against having no certs at all, not against missing the right one for IETF.
133
+ # Oh well.
134
+ warn "** Configuration problem with OpenSSL certificate store."
135
+ warn "** You may want to examine #{OpenSSL::X509::DEFAULT_CERT_FILE}"
136
+ warn "** and #{OpenSSL::X509::DEFAULT_CERT_DIR}."
137
+ warn "** Activating suboptimal workaround."
138
+ warn "** Occasionally run `certified-update` to maintain that workaround."
139
+ require 'certified'
140
+ end
141
+ end
142
+ end
143
+
144
+ RE_NL = /(?:\n|\r|\r\n)/
145
+ RE_SECTION = /---(?: +(\w+)(-?))?\s*#{RE_NL}(.*?#{RE_NL})(?=---(?:\s+\w+-?)?\s*#{RE_NL}|\Z)/m
146
+
147
+ NMDTAGS = ["{:/nomarkdown}\n\n", "\n\n{::nomarkdown}\n"]
148
+
149
+ NORMINFORM = { "!" => :normative, "?" => :informative }
150
+
151
+ def yaml_load(input, *args)
152
+ if YAML.respond_to?(:safe_load)
153
+ begin
154
+ YAML.safe_load(input, *args)
155
+ rescue ArgumentError
156
+ YAML.safe_load(input, permitted_classes: args[0], permitted_symbols: args[1], aliases: args[2])
157
+ end
158
+ else
159
+ YAML.load(input)
160
+ end
161
+ end
162
+
163
+ def process_kramdown_options(coding_override = nil,
164
+ smart_quotes = nil, typographic_symbols = nil,
165
+ header_kramdown_options = nil)
166
+
167
+ ascii_target = coding_override && coding_override =~ /ascii/
168
+ suppress_typography = ascii_target || $options.v3
169
+ entity_output = ascii_target ? :numeric : :as_char;
170
+
171
+ options = {input: 'RFC2629Kramdown', entity_output: entity_output, link_defs: {}}
172
+
173
+ if smart_quotes.nil? && suppress_typography
174
+ smart_quotes = false
175
+ end
176
+ if smart_quotes == false
177
+ smart_quotes = ["'".ord, "'".ord, '"'.ord, '"'.ord]
178
+ end
179
+ case smart_quotes
180
+ when Array
181
+ options[:smart_quotes] = smart_quotes
182
+ when nil, true
183
+ # nothin
184
+ else
185
+ warn "*** Can't deal with smart_quotes value #{smart_quotes.inspect}"
186
+ end
187
+
188
+ if typographic_symbols.nil? && suppress_typography
189
+ typographic_symbols = false
190
+ end
191
+ if typographic_symbols == false
192
+ typographic_symbols = Hash[::Kramdown::Parser::Kramdown::TYPOGRAPHIC_SYMS.map { |k, v|
193
+ if Symbol === v
194
+ [v.intern, k]
195
+ end
196
+ }.compact]
197
+ end
198
+ # warn [:TYPOGRAPHIC_SYMBOLS, typographic_symbols].to_yaml
199
+ case typographic_symbols
200
+ when Hash
201
+ options[:typographic_symbols] = typographic_symbols
202
+ when nil, true
203
+ # nothin
204
+ else
205
+ warn "*** Can't deal with typographic_symbols value #{typographic_symbols.inspect}"
206
+ end
207
+
208
+ if header_kramdown_options
209
+ options.merge! header_kramdown_options
210
+ end
211
+
212
+ $global_markdown_options = options # For nested calls in bibref annotation processing and xref text
213
+
214
+ options
215
+ end
216
+
217
+ XREF_SECTIONS_RE = ::Kramdown::Parser::RFC2629Kramdown::SECTIONS_RE
218
+ XSR_PREFIX = "#{XREF_SECTIONS_RE} of "
219
+ XSR_SUFFIX = ", (#{XREF_SECTIONS_RE})| \\((#{XREF_SECTIONS_RE})\\)"
220
+ XREF_TXT = ::Kramdown::Parser::RFC2629Kramdown::XREF_TXT
221
+ XREF_TXT_SUFFIX = " \\(#{XREF_TXT}\\)"
222
+
223
+ def spacify_re(s)
224
+ s.gsub(' ', '[\u00A0\s]+')
225
+ end
226
+
227
+ def xml_from_sections(input)
228
+
229
+ unless ENV["KRAMDOWN_NO_SOURCE"]
230
+ require 'kramdown-rfc/gzip-clone'
231
+ require 'base64'
232
+ compressed_input = Gzip.compress(input)
233
+ $source = Base64.encode64(compressed_input)
234
+ end
235
+
236
+ sections = input.scan(RE_SECTION)
237
+ # resulting in an array; each section is [section-label, nomarkdown-flag, section-text]
238
+
239
+ # the first section is a YAML with front matter parameters (don't put a label here)
240
+ # We put back the "---" plus gratuitous blank lines to hack the line number in errors
241
+ yaml_in = input[/---\s*/] << sections.shift[2]
242
+ ps = KramdownRFC::ParameterSet.new(yaml_load(yaml_in, [Date], [], true))
243
+
244
+ if v = ps[:v]
245
+ warn "*** unsupported RFCXML version #{v}" if v != 3
246
+ if $options.v2
247
+ warn "*** command line --v2 wins over document's 'v: #{v}'"
248
+ else
249
+ $options.v3 = true
250
+ $options.v = 3
251
+ ps.default!(:stand_alone, true)
252
+ ps.default!(:ipr, "trust200902")
253
+ ps.default!(:pi, {"toc" => true, "sortrefs" => true, "symrefs" => true})
254
+ end
255
+ end
256
+
257
+ coding_override = ps.has(:coding)
258
+ smart_quotes = ps[:smart_quotes]
259
+ typographic_symbols = ps[:typographic_symbols]
260
+ header_kramdown_options = ps[:kramdown_options]
261
+
262
+ kramdown_options = process_kramdown_options(coding_override,
263
+ smart_quotes, typographic_symbols,
264
+ header_kramdown_options)
265
+
266
+ # all the other sections are put in a Hash, possibly concatenated from parts there
267
+ sechash = Hash.new{ |h,k| h[k] = ""}
268
+ snames = [] # a stack of section names
269
+ sections.each do |sname, nmdflag, text|
270
+ # warn [:SNAME, sname, nmdflag, text[0..10]].inspect
271
+ nmdin, nmdout = {
272
+ "-" => ["", ""], # stay in nomarkdown
273
+ "" => NMDTAGS, # pop out temporarily
274
+ }[nmdflag || ""]
275
+ if sname
276
+ snames << sname # "--- label" -> push label (now current)
277
+ else
278
+ snames.pop # just "---" -> pop label (previous now current)
279
+ end
280
+ sechash[snames.last] << "#{nmdin}#{text}#{nmdout}"
281
+ end
282
+
283
+ ref_replacements = { }
284
+ anchor_to_bibref = { }
285
+
286
+ [:ref, :normative, :informative].each do |sn|
287
+ if refs = ps.has(sn)
288
+ warn "*** bad section #{sn}: #{refs.inspect}" unless refs.respond_to? :each
289
+ refs.each do |k, v|
290
+ if v.respond_to? :to_str
291
+ if bibtagsys(v) # enable "foo: RFC4711" as a custom anchor definition
292
+ anchor_to_bibref[k] = v.to_str
293
+ end
294
+ ref_replacements[v.to_str] = k
295
+ end
296
+ if Hash === v
297
+ if aliasname = v.delete("-")
298
+ ref_replacements[aliasname] = k
299
+ end
300
+ if bibref = v.delete("=")
301
+ anchor_to_bibref[k] = bibref
302
+ end
303
+ end
304
+ end
305
+ end
306
+ end
307
+ open_refs = ps[:ref] || { } # consumed
308
+
309
+ norm_ref = { }
310
+
311
+ # convenience replacement of {{-coap}} with {{I-D.ietf-core-coap}}
312
+ # collect normative/informative tagging {{!RFC2119}} {{?RFC4711}}
313
+ sechash.each do |k, v|
314
+ next if k == "fluff"
315
+ v.gsub!(/{{(#{
316
+ spacify_re(XSR_PREFIX)
317
+ })?(?:([?!])(-)?|(-))([\w._\-]+)(?:=([\w.\/_\-]+))?(#{
318
+ XREF_TXT_SUFFIX
319
+ })?(#{
320
+ spacify_re(XSR_SUFFIX)
321
+ })?}}/) do |match|
322
+ xsr_prefix = $1
323
+ norminform = $2
324
+ replacing = $3 || $4
325
+ word = $5
326
+ bibref = $6
327
+ xrt_suffix = $7
328
+ xsr_suffix = $8
329
+ if replacing
330
+ if new = ref_replacements[word]
331
+ word = new
332
+ else
333
+ warn "*** no alias replacement for {{-#{word}}}"
334
+ word = "-#{word}"
335
+ end
336
+ end # now, word is the anchor
337
+ if bibref
338
+ if old = anchor_to_bibref[word]
339
+ if bibref != old
340
+ warn "*** conflicting definitions for xref #{word}: #{old} != #{bibref}"
341
+ end
342
+ else
343
+ anchor_to_bibref[word] = bibref
344
+ end
345
+ end
346
+
347
+ # things can be normative in one place and informative in another -> normative
348
+ # collect norm/inform above and assign it by priority here
349
+ if norminform
350
+ norm_ref[word] ||= norminform == '!' # one normative ref is enough
351
+ end
352
+ "{{#{xsr_prefix}#{word}#{xrt_suffix}#{xsr_suffix}}}"
353
+ end
354
+ end
355
+
356
+ [:normative, :informative].each do |k|
357
+ ps.rest[k.to_s] ||= { }
358
+ end
359
+
360
+ norm_ref.each do |k, v|
361
+ # could check bibtagsys here: needed if open_refs is nil or string
362
+ target = ps.has(v ? :normative : :informative)
363
+ warn "*** overwriting #{k}" if target.has_key?(k)
364
+ target[k] = open_refs[k] # add reference to normative/informative
365
+ end
366
+ # note that unused items from ref are considered OK, therefore no check for that here
367
+
368
+ # also should allow norm/inform check of other references
369
+ # {{?coap}} vs. {{!coap}} vs. {{-coap}} (undecided)
370
+ # or {{?-coap}} vs. {{!-coap}} vs. {{-coap}} (undecided)
371
+ # could require all references to be decided by a global flag
372
+ overlap = [:normative, :informative].map { |s| (ps.has(s) || { }).keys }.reduce(:&)
373
+ unless overlap.empty?
374
+ warn "*** #{overlap.join(', ')}: both normative and informative"
375
+ end
376
+
377
+ stand_alone = ps[:stand_alone]
378
+
379
+ [:normative, :informative].each do |sn|
380
+ if refs = ps[sn]
381
+ refs.each do |k, v|
382
+ href = ::Kramdown::Parser::RFC2629Kramdown.idref_cleanup(k)
383
+ kramdown_options[:link_defs][k] = ["##{href}", nil] # allow [RFC2119] in addition to {{RFC2119}}
384
+
385
+ bibref = anchor_to_bibref[k] || k
386
+ bts, url = bibtagsys(bibref, k, stand_alone)
387
+ if bts && (!v || v == {} || v.respond_to?(:to_str))
388
+ if stand_alone
389
+ a = %{{: anchor="#{k}"}}
390
+ sechash[sn.to_s] << %{\n#{NMDTAGS[0]}\n![:include:](#{bts})#{a}\n#{NMDTAGS[1]}\n}
391
+ else
392
+ bts.gsub!('/', '_')
393
+ (ps.rest["bibxml"] ||= []) << [bts, url]
394
+ sechash[sn.to_s] << %{&#{bts};\n} # ???
395
+ end
396
+ else
397
+ unless v && Hash === v
398
+ warn "*** don't know how to expand ref #{k}"
399
+ next
400
+ end
401
+ if bts && !v.delete("override")
402
+ warn "*** warning: explicit settings completely override canned bibxml in reference #{k}"
403
+ end
404
+ sechash[sn.to_s] << KramdownRFC::ref_to_xml(href, v)
405
+ end
406
+ end
407
+ end
408
+ end
409
+
410
+ erbfilename = File.expand_path '../../../data/kramdown-rfc2629.erb', __FILE__
411
+ erbfile = File.read(erbfilename, coding: "UTF-8")
412
+ erb = ERB.trim_new(erbfile, '-')
413
+ # remove redundant nomarkdown pop outs/pop ins as they confuse kramdown
414
+ input = erb.result(binding).gsub(%r"{::nomarkdown}\s*{:/nomarkdown}"m, "")
415
+ ps.warn_if_leftovers
416
+ sechash.delete("fluff") # fluff is a "commented out" section
417
+ if !sechash.empty? # any sections unused by the ERb file?
418
+ warn "*** sections left #{sechash.keys.inspect}!"
419
+ end
420
+
421
+ [input, kramdown_options, coding_override]
422
+ end
423
+
424
+ XML_RESOURCE_ORG_PREFIX = Kramdown::Converter::Rfc2629::XML_RESOURCE_ORG_PREFIX
425
+
426
+ # return XML entity name, url, rewrite_anchor flag
427
+ def bibtagsys(bib, anchor=nil, stand_alone=true)
428
+ if bib =~ /\Arfc(\d+)/i
429
+ rfc4d = "%04d" % $1.to_i
430
+ [bib.upcase,
431
+ "#{XML_RESOURCE_ORG_PREFIX}/bibxml/reference.RFC.#{rfc4d}.xml"]
432
+ elsif $options.v3 && bib =~ /\A(bcp|std)(\d+)/i
433
+ n4d = "%04d" % $2.to_i
434
+ [bib.upcase,
435
+ "#{XML_RESOURCE_ORG_PREFIX}/bibxml-rfcsubseries-new/reference.#{$1.upcase}.#{n4d}.xml"]
436
+ elsif bib =~ /\A([-A-Z0-9]+)\./ &&
437
+ (xro = Kramdown::Converter::Rfc2629::XML_RESOURCE_ORG_MAP[$1])
438
+ dir, _ttl, rewrite_anchor = xro
439
+ bib1 = ::Kramdown::Parser::RFC2629Kramdown.idref_cleanup(bib)
440
+ if anchor && bib1 != anchor
441
+ if rewrite_anchor
442
+ a = %{?anchor=#{anchor}}
443
+ else
444
+ if !stand_alone
445
+ warn "*** selecting a custom anchor '#{anchor}' for '#{bib1}' requires stand_alone mode"
446
+ warn " the output will need manual editing to correct this"
447
+ end
448
+ end
449
+ end
450
+ [bib1,
451
+ "#{XML_RESOURCE_ORG_PREFIX}/#{dir}/reference.#{bib}.xml#{a}"]
452
+ end
453
+ end
454
+
455
+ def read_encodings
456
+ encfilename = File.expand_path '../../../data/encoding-fallbacks.txt', __FILE__
457
+ encfile = File.read(encfilename, coding: "UTF-8")
458
+ Hash[encfile.lines.map{|l|
459
+ l.chomp!;
460
+ x, s = l.split(" ", 2)
461
+ [x.hex.chr(Encoding::UTF_8), s || " "]}]
462
+ end
463
+
464
+ FALLBACK = read_encodings
465
+
466
+ def expand_tabs(s, tab_stops = 8)
467
+ s.gsub(/([^\t\n]*)\t/) do
468
+ $1 + " " * (tab_stops - ($1.size % tab_stops))
469
+ end
470
+ end
471
+
472
+
473
+ require 'optparse'
474
+ require 'ostruct'
475
+
476
+ $options ||= OpenStruct.new
477
+ op = OptionParser.new do |opts|
478
+ opts.banner = <<BANNER
479
+ Usage: kramdown-rfc2629 [options] file.md|file.mkd > file.xml
480
+ Version: #{KDRFC_VERSION}
481
+ BANNER
482
+ opts.on("-V", "--version", "Show version and exit") do |v|
483
+ puts "kramdown-rfc2629 #{KDRFC_VERSION}"
484
+ exit
485
+ end
486
+ opts.on("-H", "--help", "Show option summary and exit") do |v|
487
+ puts opts
488
+ exit
489
+ end
490
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
491
+ $options.verbose = v
492
+ end
493
+ opts.on("-3", "--[no-]v3", "Use RFCXML v3 processing rules") do |v|
494
+ $options.v3 = v
495
+ end
496
+ opts.on("-2", "--[no-]v2", "Use RFCXML v2 processing rules") do |v|
497
+ $options.v2 = v
498
+ end
499
+ end
500
+ op.parse!
501
+
502
+ if $options.v2 && $options.v3
503
+ warn "*** can't have v2 and eat v3 cake"
504
+ $options.v2 = false
505
+ end
506
+
507
+ if $options.v3.nil? && !$options.v2
508
+ if Time.now.to_i >= 1645567342 # Time.parse("2022-02-22T22:02:22Z").to_i
509
+ $options.v3 = true # new default from the above date
510
+ end
511
+ end
512
+
513
+ warn "*** v2 #{$options.v2.inspect} v3 #{$options.v3.inspect}" if $options.verbose
514
+
515
+ input = ARGF.read
516
+ if input[0] == "\uFEFF"
517
+ warn "*** There is a leading byte order mark. Ignored."
518
+ input[0..0] = ''
519
+ end
520
+ if input[-1] != "\n"
521
+ # warn "*** added missing newline at end"
522
+ input << "\n" # fix #26
523
+ end
524
+ process_includes(input) unless ENV["KRAMDOWN_SAFE"]
525
+ input.gsub!(/^\{::boilerplate\s+(.*?)\}/) {
526
+ boilerplate($1)
527
+ }
528
+ if input =~ /[\t]/
529
+ warn "*** Input contains HT (\"tab\") characters. Undefined behavior will ensue."
530
+ input = expand_tabs(input)
531
+ end
532
+
533
+ if input =~ /\A---/ # this is a sectionized file
534
+ do_the_tls_dance unless ENV["KRAMDOWN_DONT_VERIFY_HTTPS"]
535
+ input, options, coding_override = xml_from_sections(input)
536
+ else
537
+ options = process_kramdown_options # all default
538
+ end
539
+ if input =~ /\A<\?xml/ # if this is a whole XML file, protect it
540
+ input = "{::nomarkdown}\n#{input}\n{:/nomarkdown}\n"
541
+ end
542
+
543
+ if $options.v3_used && !$options.v3
544
+ warn $options.v3_used
545
+ $options.v3_used = nil
546
+ $options.v3 = true
547
+ end
548
+
549
+ if coding_override
550
+ input = input.encode(Encoding.find(coding_override), fallback: FALLBACK)
551
+ end
552
+
553
+ # 1.4.17: because of UTF-8 bibxml files, kramdown always needs to see UTF-8 (!)
554
+ if input.encoding != Encoding::UTF_8
555
+ input = input.encode(Encoding::UTF_8)
556
+ end
557
+
558
+ # warn "options: #{options.inspect}"
559
+ doc = Kramdown::Document.new(input, options)
560
+ $stderr.puts doc.warnings.to_yaml unless doc.warnings.empty?
561
+ output = doc.to_rfc2629
562
+
563
+ if $options.v3_used && !$options.v3
564
+ warn $options.v3_used
565
+ $options.v3 = true
566
+ end
567
+
568
+ if coding_override
569
+ output = output.encode(Encoding.find(coding_override), fallback: FALLBACK)
570
+ end
571
+
572
+ puts output
@@ -18,7 +18,8 @@ class KDRFC
18
18
  KDRFC_PREPEND = [ENV["KDRFC_PREPEND"]].compact
19
19
 
20
20
  def v3_flag?
21
- @options.v3 ? ["--v3"] : []
21
+ [*(@options.v3 ? ["--v3"] : []),
22
+ *(@options.v2 ? ["--v2"] : [])]
22
23
  end
23
24
 
24
25
  def process_mkd(input, output)
@@ -79,6 +80,7 @@ MODE_AS_FORMAT = {
79
80
  }
80
81
  }
81
82
 
83
+ # XXX move to author-tools@ietf.org API
82
84
  def process_xml_remotely(input, output, *flags)
83
85
  warn "* converting remotely from xml #{input} to txt #{output}" if @options.verbose
84
86
  format = flags[0] || "--text"
@@ -14,7 +14,15 @@ module KramdownRFC
14
14
  @f.delete(pn.to_s)
15
15
  end
16
16
  def []=(pn, val)
17
- @f[pn] = val
17
+ @f[pn.to_s] = val
18
+ end
19
+ def default(pn, &block)
20
+ @f.fetch(pn.to_s, &block)
21
+ end
22
+ def default!(pn, value)
23
+ default(pn) {
24
+ @f[pn.to_s] = value
25
+ }
18
26
  end
19
27
  def has(pn)
20
28
  @f[pn.to_s]
@@ -38,7 +46,19 @@ module KramdownRFC
38
46
  def ele(pn, attr=nil, defcontent=nil, markdown=false)
39
47
  val, an = van(pn)
40
48
  val ||= defcontent
49
+ val = [val] if Hash === val
41
50
  Array(val).map do |val1|
51
+ a = Array(attr).dup
52
+ if Hash === val1
53
+ val1.each do |k, v|
54
+ if k == ":"
55
+ val1 = v
56
+ else
57
+ k = Kramdown::Element.attrmangle(k) || k
58
+ a.unshift(%{#{k}="#{escattr(v)}"})
59
+ end
60
+ end
61
+ end
42
62
  v = val1.to_s.strip
43
63
  contents =
44
64
  if markdown
@@ -46,7 +66,7 @@ module KramdownRFC
46
66
  else
47
67
  escape_html(v)
48
68
  end
49
- %{<#{[an, *Array(attr).map(&:to_s)].join(" ").strip}>#{contents}</#{an}>}
69
+ %{<#{[an, *a.map(&:to_s)].join(" ").strip}>#{contents}</#{an}>}
50
70
  end.join(" ")
51
71
  end
52
72
  def arr(an, converthash=true, must_have_one=false, &block)