metanorma-mpfd 0.0.2
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 +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +17 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.adoc +1 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/rspec +18 -0
- data/bin/setup +8 -0
- data/lib/asciidoctor/mpfd/biblio.rng +836 -0
- data/lib/asciidoctor/mpfd/converter.rb +204 -0
- data/lib/asciidoctor/mpfd/isodoc.rng +1041 -0
- data/lib/asciidoctor/mpfd/isostandard.rng +1001 -0
- data/lib/asciidoctor/mpfd/pdf.js +31 -0
- data/lib/asciidoctor/mpfd/rsd.rng +212 -0
- data/lib/asciidoctor/mpfd/section.rb +94 -0
- data/lib/asciidoctor/mpfd/version.rb +5 -0
- data/lib/asciidoctor/mpfd.rb +9 -0
- data/lib/isodoc/mpfd/html/header.html +184 -0
- data/lib/isodoc/mpfd/html/html_rsd_intro.html +8 -0
- data/lib/isodoc/mpfd/html/html_rsd_titlepage.html +58 -0
- data/lib/isodoc/mpfd/html/htmlstyle.scss +1094 -0
- data/lib/isodoc/mpfd/html/logo.jpg +0 -0
- data/lib/isodoc/mpfd/html/logo.svg +1 -0
- data/lib/isodoc/mpfd/html/mpfa-logo-no-text@4x.png +0 -0
- data/lib/isodoc/mpfd/html/mpfa-logo@4x.png +0 -0
- data/lib/isodoc/mpfd/html/rsd.scss +564 -0
- data/lib/isodoc/mpfd/html/scripts.html +82 -0
- data/lib/isodoc/mpfd/html/word_rsd_intro.html +3 -0
- data/lib/isodoc/mpfd/html/word_rsd_titlepage.html +42 -0
- data/lib/isodoc/mpfd/html/wordstyle.scss +1096 -0
- data/lib/isodoc/mpfd/html_convert.rb +370 -0
- data/lib/isodoc/mpfd/i18n-en.yaml +1 -0
- data/lib/isodoc/mpfd/metadata.rb +98 -0
- data/lib/isodoc/mpfd/pdf_convert.rb +367 -0
- data/lib/isodoc/mpfd/word_convert.rb +347 -0
- data/lib/metanorma/mpfd/processor.rb +43 -0
- data/lib/metanorma/mpfd.rb +7 -0
- data/lib/metanorma-mpfd.rb +11 -0
- data/metanorma-mpfd.gemspec +46 -0
- metadata +326 -0
@@ -0,0 +1,347 @@
|
|
1
|
+
require "isodoc"
|
2
|
+
require_relative "metadata"
|
3
|
+
|
4
|
+
module IsoDoc
|
5
|
+
module Mpfd
|
6
|
+
# A {Converter} implementation that generates Word output, and a document
|
7
|
+
# schema encapsulation of the document for validation
|
8
|
+
|
9
|
+
class WordConvert < IsoDoc::WordConvert
|
10
|
+
def rsd_html_path(file)
|
11
|
+
File.join(File.dirname(__FILE__), File.join("html", file))
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(options)
|
15
|
+
super
|
16
|
+
@wordstylesheet = generate_css(rsd_html_path("wordstyle.scss"), false, default_fonts(options))
|
17
|
+
@standardstylesheet = generate_css(rsd_html_path("rsd.scss"), false, default_fonts(options))
|
18
|
+
@header = rsd_html_path("header.html")
|
19
|
+
@wordcoverpage = rsd_html_path("word_rsd_titlepage.html")
|
20
|
+
@wordintropage = rsd_html_path("word_rsd_intro.html")
|
21
|
+
@ulstyle = "l3"
|
22
|
+
@olstyle = "l2"
|
23
|
+
system "cp #{rsd_html_path('logo.jpg')} logo.jpg"
|
24
|
+
system "cp #{rsd_html_path('mpfa-logo-no-text@4x.png')} mpfa-logo-no-text@4x.png"
|
25
|
+
@files_to_delete << "logo.jpg"
|
26
|
+
@files_to_delete << "mpfa-logo-no-text@4x.png"
|
27
|
+
end
|
28
|
+
|
29
|
+
def default_fonts(options)
|
30
|
+
b = options[:bodyfont] ||
|
31
|
+
(options[:script] == "Hans" ? '"SimSun",serif' :
|
32
|
+
'"Arial",sans-serif')
|
33
|
+
h = options[:headerfont] ||
|
34
|
+
(options[:script] == "Hans" ? '"SimHei",sans-serif' :
|
35
|
+
'"Arial",sans-serif')
|
36
|
+
m = options[:monospacefont] || '"Courier New",monospace'
|
37
|
+
"$bodyfont: #{b};\n$headerfont: #{h};\n$monospacefont: #{m};\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
def metadata_init(lang, script, labels)
|
41
|
+
@meta = Metadata.new(lang, script, labels)
|
42
|
+
end
|
43
|
+
|
44
|
+
def make_body(xml, docxml)
|
45
|
+
body_attr = { lang: "EN-US", link: "blue", vlink: "#954F72" }
|
46
|
+
xml.body **body_attr do |body|
|
47
|
+
make_body1(body, docxml)
|
48
|
+
make_body2(body, docxml)
|
49
|
+
make_body3(body, docxml)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def make_body2(body, docxml)
|
54
|
+
body.div **{ class: "WordSection2" } do |div2|
|
55
|
+
info docxml, div2
|
56
|
+
div2.p { |p| p << " " } # placeholder
|
57
|
+
end
|
58
|
+
#body.br **{ clear: "all", style: "page-break-before:auto;mso-break-type:section-break;" }
|
59
|
+
section_break(body)
|
60
|
+
end
|
61
|
+
|
62
|
+
def title(isoxml, _out)
|
63
|
+
main = isoxml&.at(ns("//title[@language='en']"))&.text
|
64
|
+
set_metadata(:doctitle, main)
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_header(filename, dir)
|
68
|
+
return unless @header
|
69
|
+
template = Liquid::Template.parse(File.read(@header, encoding: "UTF-8"))
|
70
|
+
meta = @meta.get
|
71
|
+
meta[:filename] = filename
|
72
|
+
params = meta.map { |k, v| [k.to_s, v] }.to_h
|
73
|
+
File.open("header.html", "w") { |f| f.write(template.render(params)) }
|
74
|
+
@files_to_delete << "header.html"
|
75
|
+
"header.html"
|
76
|
+
end
|
77
|
+
|
78
|
+
def header_strip(h)
|
79
|
+
h = h.to_s.gsub(%r{<br/>}, " ").sub(/<\/?h[12][^>]*>/, "")
|
80
|
+
h1 = to_xhtml_fragment(h.dup)
|
81
|
+
h1.traverse do |x|
|
82
|
+
x.replace(" ") if x.name == "span" &&
|
83
|
+
/mso-tab-count/.match(x["style"])
|
84
|
+
x.remove if x.name == "span" && x["class"] == "MsoCommentReference"
|
85
|
+
x.remove if x.name == "a" && x["epub:type"] == "footnote"
|
86
|
+
x.replace(x.children) if x.name == "a"
|
87
|
+
end
|
88
|
+
from_xhtml(h1)
|
89
|
+
end
|
90
|
+
|
91
|
+
def info(isoxml, out)
|
92
|
+
@meta.security isoxml, out
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
def annex_name(annex, name, div)
|
97
|
+
div.h1 **{ class: "Annex" } do |t|
|
98
|
+
t << "#{get_anchors[annex['id']][:label]} "
|
99
|
+
t << "<b>#{name.text}</b>"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def annex_name_lbl(clause, num)
|
104
|
+
obl = l10n("(#{@inform_annex_lbl})")
|
105
|
+
obl = l10n("(#{@norm_annex_lbl})") if clause["obligation"] == "normative"
|
106
|
+
l10n("<b>#{@annex_lbl} #{num}</b> #{obl}")
|
107
|
+
end
|
108
|
+
|
109
|
+
def pre_parse(node, out)
|
110
|
+
out.pre node.text # content.gsub(/</, "<").gsub(/>/, ">")
|
111
|
+
end
|
112
|
+
|
113
|
+
def term_defs_boilerplate(div, source, term, preface)
|
114
|
+
if source.empty? && term.nil?
|
115
|
+
div << @no_terms_boilerplate
|
116
|
+
else
|
117
|
+
div << term_defs_boilerplate_cont(source, term)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def i18n_init(lang, script)
|
122
|
+
super
|
123
|
+
@annex_lbl = "Appendix"
|
124
|
+
end
|
125
|
+
|
126
|
+
def error_parse(node, out)
|
127
|
+
# catch elements not defined in ISO
|
128
|
+
case node.name
|
129
|
+
when "pre"
|
130
|
+
pre_parse(node, out)
|
131
|
+
when "keyword"
|
132
|
+
out.span node.text, **{ class: "keyword" }
|
133
|
+
else
|
134
|
+
super
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def fileloc(loc)
|
139
|
+
File.join(File.dirname(__FILE__), loc)
|
140
|
+
end
|
141
|
+
|
142
|
+
def i18n_init(lang, script)
|
143
|
+
super
|
144
|
+
y = if lang == "en"
|
145
|
+
YAML.load_file(File.join(File.dirname(__FILE__), "i18n-en.yaml"))
|
146
|
+
elsif lang == "zh" && script == "Hans"
|
147
|
+
YAML.load_file(File.join(File.dirname(__FILE__),
|
148
|
+
"i18n-zh-Hans.yaml"))
|
149
|
+
else
|
150
|
+
YAML.load_file(File.join(File.dirname(__FILE__), "i18n-zh-Hans.yaml"))
|
151
|
+
end
|
152
|
+
@labels = @labels.merge(y)
|
153
|
+
@clause_lbl = y["clause"]
|
154
|
+
end
|
155
|
+
|
156
|
+
def terms_defs_title(f)
|
157
|
+
return f&.at(ns("./title"))&.content
|
158
|
+
end
|
159
|
+
|
160
|
+
TERM_CLAUSE = "//preface/terms | "\
|
161
|
+
"//preface/clause[descendant::terms]".freeze
|
162
|
+
|
163
|
+
|
164
|
+
def terms_defs(isoxml, out, num)
|
165
|
+
f = isoxml.at(ns(TERM_CLAUSE)) or return num
|
166
|
+
out.div **attr_code(id: f["id"]) do |div|
|
167
|
+
clause_name(nil, terms_defs_title(f), div, nil)
|
168
|
+
f.elements.each do |e|
|
169
|
+
parse(e, div) unless %w{title source}.include? e.name
|
170
|
+
end
|
171
|
+
end
|
172
|
+
num
|
173
|
+
end
|
174
|
+
|
175
|
+
FRONT_CLAUSE = "//*[parent::preface]".freeze
|
176
|
+
#FRONT_CLAUSE = "//clause[parent::preface] | //terms[parent::preface]".freeze
|
177
|
+
|
178
|
+
def preface(isoxml, out)
|
179
|
+
isoxml.xpath(ns(FRONT_CLAUSE)).each do |c|
|
180
|
+
if c.name == "terms" then terms_defs isoxml, out, 0
|
181
|
+
else
|
182
|
+
out.div **attr_code(id: c["id"]) do |s|
|
183
|
+
clause_name(get_anchors[c['id']][:label],
|
184
|
+
c&.at(ns("./title"))&.content, s, nil)
|
185
|
+
c.elements.reject { |c1| c1.name == "title" }.each do |c1|
|
186
|
+
parse(c1, s)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def initial_anchor_names(d)
|
194
|
+
d.xpath(ns(FRONT_CLAUSE)).each do |c|
|
195
|
+
preface_names(c)
|
196
|
+
sequential_asset_names(c)
|
197
|
+
end
|
198
|
+
middle_section_asset_names(d)
|
199
|
+
clause_names(d, 0)
|
200
|
+
termnote_anchor_names(d)
|
201
|
+
termexample_anchor_names(d)
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
def middle(isoxml, out)
|
206
|
+
middle_title(out)
|
207
|
+
clause isoxml, out
|
208
|
+
annex isoxml, out
|
209
|
+
bibliography isoxml, out
|
210
|
+
end
|
211
|
+
|
212
|
+
def make_body2(body, docxml)
|
213
|
+
body.div **{ class: "WordSection2" } do |div2|
|
214
|
+
info docxml, div2
|
215
|
+
foreword docxml, div2
|
216
|
+
introduction docxml, div2
|
217
|
+
terms_defs docxml, div2, 0
|
218
|
+
div2.p { |p| p << " " } # placeholder
|
219
|
+
end
|
220
|
+
section_break(body)
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
def middle(isoxml, out)
|
225
|
+
middle_title(out)
|
226
|
+
clause isoxml, out
|
227
|
+
annex isoxml, out
|
228
|
+
bibliography isoxml, out
|
229
|
+
end
|
230
|
+
|
231
|
+
def termdef_parse(node, out)
|
232
|
+
set_termdomain("")
|
233
|
+
node.children.each { |n| parse(n, out) }
|
234
|
+
end
|
235
|
+
|
236
|
+
def annex_name_lbl(clause, num)
|
237
|
+
l10n("<b>#{@annex_lbl} #{num}</b>")
|
238
|
+
end
|
239
|
+
|
240
|
+
def clause_names(docxml, sect_num)
|
241
|
+
q = "//clause[parent::sections]"
|
242
|
+
@topnum = nil
|
243
|
+
lvl = 0
|
244
|
+
docxml.xpath(ns(q)).each do |c|
|
245
|
+
container_names(c, 0)
|
246
|
+
sect_num, lvl = sect_names(c, nil, sect_num, 0, lvl)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def container_names(clause, lvl)
|
251
|
+
if clause["container"]
|
252
|
+
@anchors[clause["id"]] =
|
253
|
+
{ label: nil, xref: clause.at(ns("./title"))&.text, level: lvl+1 }
|
254
|
+
end
|
255
|
+
clause.xpath(ns("./clause | ./term | ./terms | "\
|
256
|
+
"./definitions")).each do |c|
|
257
|
+
container_names(c, clause["container"] ? lvl+1 : lvl)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def sect_names(clause, num, i, lvl, prev_lvl)
|
262
|
+
return i if clause.nil?
|
263
|
+
curr = i
|
264
|
+
if clause["container"]
|
265
|
+
retlvl = lvl+1
|
266
|
+
else
|
267
|
+
retlvl = lvl
|
268
|
+
i+=1
|
269
|
+
curr = i
|
270
|
+
name = num.nil? ? i.to_s : "#{num}.#{i}"
|
271
|
+
@anchors[clause["id"]] = { label: name, xref: l10n("#{@clause_lbl} #{name}"), level: lvl+1 }
|
272
|
+
end
|
273
|
+
prev = lvl
|
274
|
+
j = 0
|
275
|
+
clause.xpath(ns("./clause | ./term | ./terms | "\
|
276
|
+
"./definitions")).each do |c|
|
277
|
+
if clause["container"]
|
278
|
+
i, lvl = sect_names(c, num, i, lvl, lvl)
|
279
|
+
else
|
280
|
+
j, prev = sect_names(c, name, j, lvl+1, prev)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
i = j if j >0
|
284
|
+
i = curr if lvl < prev
|
285
|
+
[i, prev]
|
286
|
+
end
|
287
|
+
|
288
|
+
def annex_naming(c, num, lvl, i)
|
289
|
+
if c["guidance"] then annex_names1(c, "#{num}E", lvl + 1)
|
290
|
+
else
|
291
|
+
i+= 1
|
292
|
+
annex_names1(c, "#{num}.#{i}", lvl + 1)
|
293
|
+
end
|
294
|
+
i
|
295
|
+
end
|
296
|
+
|
297
|
+
def annex_names(clause, num)
|
298
|
+
@anchors[clause["id"]] = { label: annex_name_lbl(clause, num),
|
299
|
+
xref: "#{@annex_lbl} #{num}", level: 1 }
|
300
|
+
i = 0
|
301
|
+
clause.xpath(ns("./clause")).each do |c|
|
302
|
+
i = annex_naming(c, num, 1, i)
|
303
|
+
end
|
304
|
+
hierarchical_asset_names(clause, num)
|
305
|
+
end
|
306
|
+
|
307
|
+
def annex_names1(clause, num, level)
|
308
|
+
@anchors[clause["id"]] = { label: num, xref: "#{@annex_lbl} #{num}",
|
309
|
+
level: level }
|
310
|
+
i = 0
|
311
|
+
clause.xpath(ns("./clause")).each do |c|
|
312
|
+
i = annex_naming(c, num, level, i)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def clause(isoxml, out)
|
317
|
+
isoxml.xpath(ns(MIDDLE_CLAUSE)).each do |c|
|
318
|
+
out.div **attr_code(id: c["id"]) do |s|
|
319
|
+
clause_name(get_anchors[c['id']][:label],
|
320
|
+
c&.at(ns("./title"))&.content, s, class: c["container"] ? "containerhdr" : nil )
|
321
|
+
c.elements.reject { |c1| c1.name == "title" }.each do |c1|
|
322
|
+
parse(c1, s)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def clause_parse_title(node, div, c1, out)
|
329
|
+
if node["inline-header"] == "true"
|
330
|
+
inline_header_title(out, node, c1)
|
331
|
+
else
|
332
|
+
attrs = { class: node["container"] ? "containerhdr" : nil }
|
333
|
+
div.send "h#{get_anchors[node['id']][:level]}", **attr_code(attrs) do |h|
|
334
|
+
lbl = get_anchors[node['id']][:label]
|
335
|
+
h << "#{lbl}. " if lbl
|
336
|
+
c1&.children&.each { |c2| parse(c2, h) }
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def ol_depth(node)
|
342
|
+
ol_style(node["type"])
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "metanorma/processor"
|
2
|
+
|
3
|
+
module Metanorma
|
4
|
+
module Mpfd
|
5
|
+
class Processor < Metanorma::Processor
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@short = :mpfd
|
9
|
+
@input_format = :asciidoc
|
10
|
+
@asciidoctor_backend = :mpfd
|
11
|
+
end
|
12
|
+
|
13
|
+
def output_formats
|
14
|
+
super.merge(
|
15
|
+
html: "html",
|
16
|
+
doc: "doc",
|
17
|
+
pdf: "pdf"
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def version
|
22
|
+
"Asciidoctor::Mpfd #{Asciidoctor::Mpfd::VERSION}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def input_to_isodoc(file)
|
26
|
+
Metanorma::Input::Asciidoc.new.process(file, @asciidoctor_backend)
|
27
|
+
end
|
28
|
+
|
29
|
+
def output(isodoc_node, outname, format, options={})
|
30
|
+
case format
|
31
|
+
when :html
|
32
|
+
IsoDoc::Mpfd::HtmlConvert.new(options).convert(outname, isodoc_node)
|
33
|
+
when :doc
|
34
|
+
IsoDoc::Mpfd::WordConvert.new(options).convert(outname, isodoc_node)
|
35
|
+
when :pdf
|
36
|
+
IsoDoc::Mpfd::PdfConvert.new(options).convert(outname, isodoc_node)
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "asciidoctor" unless defined? Asciidoctor::Converter
|
2
|
+
require_relative "asciidoctor/mpfd/converter"
|
3
|
+
require_relative "isodoc/mpfd/html_convert"
|
4
|
+
require_relative "isodoc/mpfd/word_convert"
|
5
|
+
require_relative "isodoc/mpfd/pdf_convert"
|
6
|
+
require_relative "asciidoctor/mpfd/version"
|
7
|
+
|
8
|
+
if defined? Metanorma
|
9
|
+
require_relative "metanorma/mpfd"
|
10
|
+
Metanorma::Registry.instance.register(Metanorma::Mpfd::Processor)
|
11
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "asciidoctor/mpfd/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "metanorma-mpfd"
|
7
|
+
spec.version = Asciidoctor::Mpfd::VERSION
|
8
|
+
spec.authors = ["Ribose Inc."]
|
9
|
+
spec.email = ["open.source@ribose.com"]
|
10
|
+
|
11
|
+
spec.summary = "metanorma-mpfd lets you write MPF documents in AsciiDoc."
|
12
|
+
spec.description = <<~DESCRIPTION
|
13
|
+
metanorma-mpfd lets you write MPF documents in AsciiDoc syntax.
|
14
|
+
|
15
|
+
This gem is in active development.
|
16
|
+
DESCRIPTION
|
17
|
+
|
18
|
+
spec.homepage = "https://github.com/riboseinc/metanorma-mpfd"
|
19
|
+
spec.license = "BSD-2-Clause"
|
20
|
+
|
21
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
22
|
+
f.match(%r{^(test|spec|features)/})
|
23
|
+
end
|
24
|
+
spec.bindir = "exe"
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_dependency "asciidoctor", "~> 1.5.7"
|
29
|
+
spec.add_dependency "htmlentities", "~> 4.3.4"
|
30
|
+
spec.add_dependency "nokogiri"
|
31
|
+
spec.add_dependency "asciidoctor-iso", "~> 0.10.1"
|
32
|
+
spec.add_dependency "isodoc", ">= 0.8.4"
|
33
|
+
spec.add_dependency "twitter_cldr"
|
34
|
+
|
35
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
36
|
+
spec.add_development_dependency "byebug", "~> 9.1"
|
37
|
+
spec.add_development_dependency "equivalent-xml", "~> 0.6"
|
38
|
+
spec.add_development_dependency "guard", "~> 2.14"
|
39
|
+
spec.add_development_dependency "guard-rspec", "~> 4.7"
|
40
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
41
|
+
spec.add_development_dependency "rspec", "~> 3.6"
|
42
|
+
spec.add_development_dependency "rubocop", "~> 0.50"
|
43
|
+
spec.add_development_dependency "simplecov", "~> 0.15"
|
44
|
+
spec.add_development_dependency "timecop", "~> 0.9"
|
45
|
+
spec.add_development_dependency "metanorma", "~> 0.2.6"
|
46
|
+
end
|