kramdown-rfc2629 1.3.8 → 1.3.13
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/README.md +25 -0
- data/bin/doilit +7 -1
- data/bin/kdrfc +33 -11
- data/bin/kramdown-rfc2629 +16 -3
- data/kramdown-rfc2629.gemspec +1 -2
- data/lib/kramdown-rfc2629.rb +101 -29
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe573235c508ba9d089f72b578a53ea8507b53d82a539539228982a799e830a7
|
4
|
+
data.tar.gz: b7541fa31e84a9428f18541f2bef57b63984e1b3b9c193e4c28c82cdd3e2bcbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 754f2d2d69f025e73037aaf86b6b315eb362251b3fd1c4f070adb4c7a380d9c05bef9445c863aa0e0e4c4877d5d0eb2f0e38b5b120a9a86642969b8b81673ccf
|
7
|
+
data.tar.gz: 53a5b1caa470513f06a4bf28618b35f45ae2632cda64edc3e64e2e894d91e780272da81c383d0967aa6e98553f39286f4daa8ddaf97c299397e30f7d0d423a1a
|
data/README.md
CHANGED
@@ -281,6 +281,31 @@ special support in XML2RFC), and HTML syntax of course.
|
|
281
281
|
A number of more esoteric features have recently been added.
|
282
282
|
(The minimum required version for each full feature is indicated.)
|
283
283
|
|
284
|
+
(1.3.x)
|
285
|
+
Slowly improving support for SVG generating tools for XML2RFCv3 (i.e.,
|
286
|
+
with `-3` flag).
|
287
|
+
These tools must be installed and callable from the command line.
|
288
|
+
|
289
|
+
The basic idea is to mark an input code block with one of the following
|
290
|
+
labels (language types), yielding some plaintext form in the .TXT
|
291
|
+
output and a graphical form in the .HTML output. The plaintext is the
|
292
|
+
input in some cases (e.g., ASCII art, `mscgen`), or some plaintext
|
293
|
+
output generated by the tool (e.g., `plantuml-utxt`).
|
294
|
+
|
295
|
+
Currently supported labels as of 1.3.9:
|
296
|
+
|
297
|
+
* goat, ditaa: ASCII (plaintext) art to figure conversion
|
298
|
+
* mscgen: Message Sequence Charts
|
299
|
+
* plantuml: widely used multi-purpose diagram generator
|
300
|
+
* plantuml-utxt: Like plantuml, except that a plantuml-generated
|
301
|
+
plaintext form is used
|
302
|
+
* mermaid: Very experimental; the conversion to SVG is prone to
|
303
|
+
generate black-on-black text in this version
|
304
|
+
|
305
|
+
Note that this feature does not play well with the CI (continuous
|
306
|
+
integration) support in Martin Thomson's [I-D Template][], as that may
|
307
|
+
not have the tools installed in its docker instance.
|
308
|
+
|
284
309
|
(1.2.9:)
|
285
310
|
The YAML header now allows specifying [kramdown_options][].
|
286
311
|
|
data/bin/doilit
CHANGED
@@ -39,11 +39,17 @@ ARGV.each do |doi|
|
|
39
39
|
warn "*** Usage: doilit [-c] [-v] [-h=handle|-x=xmlhandle] doi..."
|
40
40
|
exit 1
|
41
41
|
end
|
42
|
-
cite = JSON.parse(
|
42
|
+
cite = JSON.parse(URI("https://dx.doi.org/#{doi}").open(ACCEPT_CITE_JSON).read)
|
43
43
|
puts cite.to_yaml if $verbose
|
44
44
|
lit = {}
|
45
45
|
ser = lit["seriesinfo"] = {}
|
46
46
|
lit["title"] = cite["title"]
|
47
|
+
if (st = cite["subtitle"]) && Array === st # defensive
|
48
|
+
st.delete('')
|
49
|
+
if st != []
|
50
|
+
lit["title"] << ": " << st.join("; ")
|
51
|
+
end
|
52
|
+
end
|
47
53
|
if authors = cite["author"]
|
48
54
|
lit["author"] = authors.map do |au|
|
49
55
|
lau = {}
|
data/bin/kdrfc
CHANGED
@@ -3,6 +3,9 @@ require 'uri'
|
|
3
3
|
require 'net/http'
|
4
4
|
require 'open3'
|
5
5
|
|
6
|
+
# try to get this from gemspec.
|
7
|
+
KDRFC_VERSION=Gem.loaded_specs["kramdown-rfc2629"].version rescue "unknown-version"
|
8
|
+
|
6
9
|
def v3_flag?
|
7
10
|
$options.v3 ? ["--v3"] : []
|
8
11
|
end
|
@@ -46,16 +49,21 @@ def process_xml_locally(input, output, *flags)
|
|
46
49
|
end
|
47
50
|
end
|
48
51
|
|
52
|
+
XML2RFC_WEBSERVICE = ENV["KRAMDOWN_XML2RFC_WEBSERVICE"] ||
|
53
|
+
'http://xml2rfc.tools.ietf.org/cgi-bin/xml2rfc-dev.cgi'
|
54
|
+
|
55
|
+
|
49
56
|
def process_xml_remotely(input, output)
|
50
57
|
warn "* converting remotely from xml #{input} to txt #{output}" if $options.verbose
|
51
|
-
url = URI(
|
58
|
+
url = URI(XML2RFC_WEBSERVICE)
|
52
59
|
req = Net::HTTP::Post.new(url)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
60
|
+
form = [["modeAsFormat", "txt/#{"v3" if $options.v3}ascii"],
|
61
|
+
["type", "binary"],
|
62
|
+
["input", File.open(input),
|
63
|
+
{filename: "input.xml",
|
64
|
+
content_type: "text/plain"}]]
|
65
|
+
diag = ["url/form: ", url, form].inspect
|
66
|
+
req.set_form(form, 'multipart/form-data')
|
59
67
|
res = Net::HTTP::start(url.hostname, url.port,
|
60
68
|
:use_ssl => url.scheme == 'https' ) {|http|
|
61
69
|
http.request(req)
|
@@ -64,17 +72,21 @@ def process_xml_remotely(input, output)
|
|
64
72
|
when Net::HTTPOK
|
65
73
|
case res.content_type
|
66
74
|
when 'application/octet-stream'
|
75
|
+
if res.body == ''
|
76
|
+
warn "*** HTTP response is empty with status #{res.code}, not written"
|
77
|
+
exit 1
|
78
|
+
end
|
67
79
|
File.open(output, "w") do |fo|
|
68
80
|
fo.print(res.body)
|
69
81
|
end
|
70
82
|
warn "* #{output} written" if $options.verbose
|
71
83
|
else
|
72
|
-
warn "*** HTTP response has unexpected content_type #{res.content_type} with status #{res.code}"
|
84
|
+
warn "*** HTTP response has unexpected content_type #{res.content_type} with status #{res.code}, #{diag}"
|
73
85
|
warn res.body
|
74
86
|
exit 1
|
75
87
|
end
|
76
88
|
else
|
77
|
-
warn "*** HTTP response: #{res.code}"
|
89
|
+
warn "*** HTTP response: #{res.code}, #{diag}"
|
78
90
|
exit 1
|
79
91
|
end
|
80
92
|
end
|
@@ -85,8 +97,18 @@ require 'ostruct'
|
|
85
97
|
$options = OpenStruct.new
|
86
98
|
$options.txt = true # default
|
87
99
|
op = OptionParser.new do |opts|
|
88
|
-
opts.banner =
|
89
|
-
|
100
|
+
opts.banner = <<BANNER
|
101
|
+
Usage: kdrfc [options] file.md|file.mkd|file.xml
|
102
|
+
Version: #{KDRFC_VERSION}
|
103
|
+
BANNER
|
104
|
+
opts.on("-V", "--version", "Show version and exit") do |v|
|
105
|
+
puts "kdrfc, from kramdown-rfc2629 #{KDRFC_VERSION}"
|
106
|
+
exit
|
107
|
+
end
|
108
|
+
opts.on("-H", "--help", "Show option summary and exit") do |v|
|
109
|
+
puts opts
|
110
|
+
exit
|
111
|
+
end
|
90
112
|
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
91
113
|
$options.verbose = v
|
92
114
|
end
|
data/bin/kramdown-rfc2629
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/env
|
1
|
+
#!/usr/bin/env ruby
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
require 'kramdown-rfc2629'
|
4
4
|
require 'kramdown-rfc/parameterset'
|
@@ -7,6 +7,9 @@ require 'yaml'
|
|
7
7
|
require 'erb'
|
8
8
|
require 'date'
|
9
9
|
|
10
|
+
# try to get this from gemspec.
|
11
|
+
KDRFC_VERSION=Gem.loaded_specs["kramdown-rfc2629"].version rescue "unknown-version"
|
12
|
+
|
10
13
|
Encoding.default_external = "UTF-8" # wake up, smell the coffee
|
11
14
|
|
12
15
|
def boilerplate(key)
|
@@ -287,8 +290,18 @@ require 'ostruct'
|
|
287
290
|
|
288
291
|
$options = OpenStruct.new
|
289
292
|
op = OptionParser.new do |opts|
|
290
|
-
opts.banner =
|
291
|
-
|
293
|
+
opts.banner = <<BANNER
|
294
|
+
Usage: kramdown-rfc2629 [options] file.md|file.mkd > file.xml
|
295
|
+
Version: #{KDRFC_VERSION}
|
296
|
+
BANNER
|
297
|
+
opts.on("-V", "--version", "Show version and exit") do |v|
|
298
|
+
puts "kramdown-rfc2629 #{KDRFC_VERSION}"
|
299
|
+
exit
|
300
|
+
end
|
301
|
+
opts.on("-H", "--help", "Show option summary and exit") do |v|
|
302
|
+
puts opts
|
303
|
+
exit
|
304
|
+
end
|
292
305
|
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
293
306
|
$options.verbose = v
|
294
307
|
end
|
data/kramdown-rfc2629.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
spec = Gem::Specification.new do |s|
|
2
2
|
s.name = 'kramdown-rfc2629'
|
3
|
-
s.version = '1.3.
|
3
|
+
s.version = '1.3.13'
|
4
4
|
s.summary = "Kramdown extension for generating RFC 7749 XML."
|
5
5
|
s.description = %{An RFC7749 (XML2RFC) generating backend for Thomas Leitner's
|
6
6
|
"kramdown" markdown parser. Mostly useful for RFC writers.}
|
@@ -9,7 +9,6 @@ spec = Gem::Specification.new do |s|
|
|
9
9
|
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)
|
10
10
|
s.require_path = 'lib'
|
11
11
|
s.executables = ['kramdown-rfc2629', 'doilit', 'kramdown-rfc-extract-markdown', 'kdrfc']
|
12
|
-
s.default_executable = 'kramdown-rfc2629'
|
13
12
|
s.required_ruby_version = '>= 2.3.0'
|
14
13
|
# s.requirements = 'wget'
|
15
14
|
# s.has_rdoc = true
|
data/lib/kramdown-rfc2629.rb
CHANGED
@@ -37,16 +37,27 @@ module Kramdown
|
|
37
37
|
@span_parsers.unshift(:iref)
|
38
38
|
end
|
39
39
|
|
40
|
-
XREF_START = /\{\{(.*?)\}\}/u
|
40
|
+
XREF_START = /\{\{(?:(?:\{(.*?)\}(?:\{(.*?)\})?)|(.*?))((?:\}\})|\})/u
|
41
41
|
|
42
42
|
# Introduce new {{target}} syntax for empty xrefs, which would
|
43
43
|
# otherwise be an ugly  or 
|
44
44
|
# (I'd rather use [[target]], but that somehow clashes with links.)
|
45
45
|
def parse_xref
|
46
46
|
@src.pos += @src.matched_size
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
unless @src[4] == "}}"
|
48
|
+
warn "*** #{@src[0]}: unmatched braces #{@src[4].inspect}"
|
49
|
+
end
|
50
|
+
if contact_name = @src[1]
|
51
|
+
attr = {'fullname' => contact_name}
|
52
|
+
if ascii_name = @src[2]
|
53
|
+
attr["asciiFullname"] = ascii_name
|
54
|
+
end
|
55
|
+
el = Element.new(:contact, nil, attr)
|
56
|
+
else
|
57
|
+
href = @src[3]
|
58
|
+
href = href.gsub(/\A[0-9]/) { "_#{$&}" } # can't start an IDREF with a number
|
59
|
+
el = Element.new(:xref, nil, {'target' => href})
|
60
|
+
end
|
50
61
|
@tree.children << el
|
51
62
|
end
|
52
63
|
define_parser(:xref, XREF_START, '{{')
|
@@ -150,10 +161,42 @@ module Kramdown
|
|
150
161
|
generate_id(value).gsub(/-+/, '-')
|
151
162
|
end
|
152
163
|
|
164
|
+
SVG_COLORS = Hash.new {|h, k| k}
|
165
|
+
<<COLORS.each_line {|l| k, v = l.chomp.split; SVG_COLORS[k] = v}
|
166
|
+
black #000000
|
167
|
+
silver #C0C0C0
|
168
|
+
gray #808080
|
169
|
+
white #FFFFFF
|
170
|
+
maroon #800000
|
171
|
+
red #FF0000
|
172
|
+
purple #800080
|
173
|
+
fuchsia #FF00FF
|
174
|
+
green #008000
|
175
|
+
lime #00FF00
|
176
|
+
olive #808000
|
177
|
+
yellow #FFFF00
|
178
|
+
navy #000080
|
179
|
+
blue #0000FF
|
180
|
+
teal #008080
|
181
|
+
aqua #00FFFF
|
182
|
+
COLORS
|
183
|
+
|
184
|
+
def svg_munch_id(id)
|
185
|
+
id.gsub(/[^-._A-Za-z0-9]/) {|x| "_%02X" % x.ord}
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.hex_to_lin(h)
|
189
|
+
h.to_i(16)**2.22 # approximating sRGB gamma
|
190
|
+
end
|
191
|
+
define_method :hex_to_lin, &method(:hex_to_lin)
|
192
|
+
|
193
|
+
B_W_THRESHOLD = hex_to_lin("a4") # a little brighter than 1/2 0xFF -> white
|
194
|
+
|
153
195
|
def svg_munch_color(c, fill)
|
196
|
+
c = SVG_COLORS[c]
|
154
197
|
case c
|
155
198
|
when /\A#(..)(..)(..)\z/
|
156
|
-
if
|
199
|
+
if hex_to_lin($1)*0.2126 + hex_to_lin($2)*0.7152 + hex_to_lin($3)*0.0722 >= B_W_THRESHOLD
|
157
200
|
'white'
|
158
201
|
else
|
159
202
|
'black'
|
@@ -172,9 +215,56 @@ module Kramdown
|
|
172
215
|
REXML::XPath.each(d.root, "//*[@text-rendering]") { |x| x.attributes["text-rendering"] = nil } #; warn x.inspect }
|
173
216
|
REXML::XPath.each(d.root, "//*[@stroke]") { |x| x.attributes["stroke"] = svg_munch_color(x.attributes["stroke"], false) }
|
174
217
|
REXML::XPath.each(d.root, "//*[@fill]") { |x| x.attributes["fill"] = svg_munch_color(x.attributes["fill"], true) }
|
218
|
+
REXML::XPath.each(d.root, "//*[@id]") { |x| x.attributes["id"] = svg_munch_id(x.attributes["id"]) }
|
219
|
+
## REXML::XPath.each(d.root, "//rect") { |x| x.attributes["style"] = "fill:none;stroke:black;stroke-width:1" unless x.attributes["style"] }
|
175
220
|
d.to_s
|
176
221
|
end
|
177
222
|
|
223
|
+
def memoize(meth, *args)
|
224
|
+
require 'digest'
|
225
|
+
Dir.mkdir(REFCACHEDIR) unless Dir.exists?(REFCACHEDIR)
|
226
|
+
kdrfc_version = Gem.loaded_specs["kramdown-rfc2629"].version.to_s.gsub('.', '_') rescue "UNKNOWN"
|
227
|
+
fn = "#{REFCACHEDIR}/kdrfc-#{kdrfc_version}-#{meth}-#{Digest::SHA256.hexdigest(Marshal.dump(args))[0...40]}.cache"
|
228
|
+
begin
|
229
|
+
out = Marshal.load(File.binread(fn))
|
230
|
+
rescue StandardError => e
|
231
|
+
# warn e.inspect
|
232
|
+
out = method(meth).call(*args)
|
233
|
+
File.binwrite(fn, Marshal.dump(out))
|
234
|
+
end
|
235
|
+
out
|
236
|
+
end
|
237
|
+
|
238
|
+
def svg_tool_process(t, result)
|
239
|
+
require 'tempfile'
|
240
|
+
file = Tempfile.new("kramdown-rfc")
|
241
|
+
file.write(result)
|
242
|
+
file.close
|
243
|
+
case t
|
244
|
+
when "goat"
|
245
|
+
result1, _s = Open3.capture2("goat #{file.path}", stdin_data: result);
|
246
|
+
when "ditaa" # XXX: This needs some form of option-setting
|
247
|
+
result1, _s = Open3.capture2("ditaa #{file.path} --svg -o -", stdin_data: result);
|
248
|
+
when "mscgen"
|
249
|
+
result1, _s = Open3.capture2("mscgen -T svg -i #{file.path} -o -", stdin_data: result);
|
250
|
+
when "mermaid"
|
251
|
+
result1, _s = Open3.capture2("mmdc -i #{file.path}", stdin_data: result); # -b transparent
|
252
|
+
outpath = file.path + ".svg"
|
253
|
+
result1 = File.read(outpath)
|
254
|
+
File.unlink(outpath)
|
255
|
+
when "plantuml", "plantuml-utxt"
|
256
|
+
plantuml = "@startuml\n#{result}\n@enduml"
|
257
|
+
result1, _s = Open3.capture2("plantuml -pipe -tsvg", stdin_data: plantuml);
|
258
|
+
result, _s = Open3.capture2("plantuml -pipe -tutxt", stdin_data: plantuml) if t == "plantuml-utxt"
|
259
|
+
end
|
260
|
+
# warn ["goat:", result1.inspect]
|
261
|
+
file.unlink
|
262
|
+
result1 = svg_clean(result1) unless t == "goat"
|
263
|
+
result1, _s = Open3.capture2("svgcheck -qa", stdin_data: result1);
|
264
|
+
# warn ["svgcheck:", result1.inspect]
|
265
|
+
[result, result1]
|
266
|
+
end
|
267
|
+
|
178
268
|
def convert_codeblock(el, indent, opts)
|
179
269
|
# el.attr['anchor'] ||= saner_generate_id(el.value) -- no longer in 1.0.6
|
180
270
|
result = el.value
|
@@ -219,30 +309,8 @@ module Kramdown
|
|
219
309
|
end
|
220
310
|
end
|
221
311
|
case t
|
222
|
-
when "goat", "ditaa", "mscgen", "plantuml", "plantuml-utxt"
|
223
|
-
|
224
|
-
file = Tempfile.new("kramdown-rfc")
|
225
|
-
file.write(result)
|
226
|
-
file.close
|
227
|
-
case t
|
228
|
-
when "goat"
|
229
|
-
result1, _s = Open3.capture2("goat #{file.path}", stdin_data: result);
|
230
|
-
when "ditaa" # XXX: This needs some form of option-setting
|
231
|
-
result1, _s = Open3.capture2("ditaa #{file.path} --svg -o -", stdin_data: result);
|
232
|
-
result1 = svg_clean(result1)
|
233
|
-
when "mscgen"
|
234
|
-
result1, _s = Open3.capture2("mscgen -T svg -i #{file.path} -o -", stdin_data: result);
|
235
|
-
result1 = svg_clean(result1)
|
236
|
-
when "plantuml", "plantuml-utxt"
|
237
|
-
plantuml = "@startuml\n#{result}\n@enduml"
|
238
|
-
result1, _s = Open3.capture2("plantuml -pipe -tsvg", stdin_data: plantuml);
|
239
|
-
result1 = svg_clean(result1)
|
240
|
-
result, _s = Open3.capture2("plantuml -pipe -tutxt", stdin_data: plantuml) if t == "plantuml-utxt"
|
241
|
-
end
|
242
|
-
# warn ["goat:", result1.inspect]
|
243
|
-
file.unlink
|
244
|
-
result1, _s = Open3.capture2("svgcheck -qa", stdin_data: result1);
|
245
|
-
# warn ["svgcheck:", result1.inspect]
|
312
|
+
when "goat", "ditaa", "mscgen", "plantuml", "plantuml-utxt", "mermaid"
|
313
|
+
result, result1 = memoize(:svg_tool_process, t, result)
|
246
314
|
"#{' '*indent}<figure#{el_html_attributes(el)}><artset><artwork #{html_attributes(artwork_attr.merge("type"=> "svg"))}>#{result1.sub(/.*?<svg/m, "<svg")}</artwork><artwork #{html_attributes(artwork_attr.merge("type"=> "ascii-art"))}><![CDATA[#{result}#{result =~ /\n\Z/ ? '' : "\n"}]]></artwork></artset></figure>\n"
|
247
315
|
else
|
248
316
|
"#{' '*indent}<figure#{el_html_attributes(el)}><artwork#{html_attributes(artwork_attr)}><![CDATA[#{result}#{result =~ /\n\Z/ ? '' : "\n"}]]></artwork></figure>\n"
|
@@ -467,6 +535,10 @@ module Kramdown
|
|
467
535
|
end
|
468
536
|
end
|
469
537
|
|
538
|
+
def convert_contact(el, indent, opts)
|
539
|
+
"<contact#{el_html_attributes(el)}/>"
|
540
|
+
end
|
541
|
+
|
470
542
|
REFCACHEDIR = ENV["KRAMDOWN_REFCACHEDIR"] || ".refcache"
|
471
543
|
|
472
544
|
# warn "*** REFCACHEDIR #{REFCACHEDIR}"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kramdown-rfc2629
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carsten Bormann
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: kramdown
|
@@ -67,7 +67,7 @@ homepage: http://github.com/cabo/kramdown-rfc2629
|
|
67
67
|
licenses:
|
68
68
|
- MIT
|
69
69
|
metadata: {}
|
70
|
-
post_install_message:
|
70
|
+
post_install_message:
|
71
71
|
rdoc_options: []
|
72
72
|
require_paths:
|
73
73
|
- lib
|
@@ -83,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
83
|
version: '0'
|
84
84
|
requirements: []
|
85
85
|
rubygems_version: 3.1.2
|
86
|
-
signing_key:
|
86
|
+
signing_key:
|
87
87
|
specification_version: 4
|
88
88
|
summary: Kramdown extension for generating RFC 7749 XML.
|
89
89
|
test_files: []
|