metanorma-utils 2.0.7 → 2.0.8
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/.rubocop.yml +23 -2
- data/lib/metanorma-utils.rb +2 -0
- data/lib/utils/anchor.rb +1 -1
- data/lib/utils/asciidoctor_table_buffer.rb +259 -0
- data/lib/utils/gc_budget.rb +22 -0
- data/lib/utils/main.rb +8 -2
- data/lib/utils/version.rb +1 -1
- data/lib/utils/xml.rb +16 -4
- data/metanorma-utils.gemspec +2 -2
- metadata +9 -8
- data/.hound.yml +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4cb1c9e13d72f7f7310918941cd4df7243968915307e4ec830160237b715ea54
|
|
4
|
+
data.tar.gz: 9219b32ec8d1672f82d5c3e7d88f1903d39e4e19ddb481f8c7748c55810779c3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f7821e4c4b22b02182b5a66e561fcccfd8a0dc2cfdb304f347eab986d5c5a444565380affcb80a11c6e96a1fcf1cedf255b8c82d8eaccd3dab2937664f66622b
|
|
7
|
+
data.tar.gz: f1a9df51ff4cf0b8c3376407b63f45ef4766845bd3c6659e4f479956df60bfeba1307e86ae91ebc7a033c45a88d25199e0ce9dc79f5b636d9f922a9466eec7cd
|
data/.rubocop.yml
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
# Auto-generated by Cimas: Do not edit it manually!
|
|
2
2
|
# See https://github.com/metanorma/cimas
|
|
3
3
|
inherit_from:
|
|
4
|
-
- https://raw.githubusercontent.com/riboseinc/oss-guides/
|
|
4
|
+
- https://raw.githubusercontent.com/riboseinc/oss-guides/main/ci/rubocop.yml
|
|
5
|
+
# .rubocop_todo.yml MUST be the last entry. inherit_from is last-wins:
|
|
6
|
+
# if listed before oss-guides, the shared config's stricter Metrics/
|
|
7
|
+
# (MethodLength, BlockLength, etc.) rules override the todo's per-file
|
|
8
|
+
# grandfathering, and the todo becomes inert on those cops. Empirically
|
|
9
|
+
# verified on suma 2026-07-06: with todo listed first, 34 Metrics/* offenses
|
|
10
|
+
# remained; moved last, cleared. Every gem in the metanorma-org fleet
|
|
11
|
+
# already ships a `.rubocop_todo.yml` on its live tree (audited 2026-07-06,
|
|
12
|
+
# 54/54 have it), so this reference is safe to emit unconditionally.
|
|
13
|
+
- .rubocop_todo.yml
|
|
14
|
+
|
|
15
|
+
# Rubocop plugins enabled centrally so every metanorma-org gem picks them up
|
|
16
|
+
# on cimas sync — best practice belongs at the shared-template layer, not
|
|
17
|
+
# per-repo. Per ronaldtse feedback on metanorma/ci#332.
|
|
18
|
+
plugins:
|
|
19
|
+
- rubocop-rspec
|
|
20
|
+
- rubocop-performance
|
|
21
|
+
- rubocop-rake
|
|
5
22
|
|
|
6
23
|
# local repo-specific modifications
|
|
7
24
|
# ...
|
|
8
25
|
|
|
9
26
|
AllCops:
|
|
10
|
-
|
|
27
|
+
# 3.3 matches the org-wide minimum being pushed via #274 (Raise minimum
|
|
28
|
+
# Ruby version to 3.3 due to EOL of 3.2 on 2026-03-31). Was 3.4 before
|
|
29
|
+
# this commit — 3.4 was above the org's stated minimum and would have
|
|
30
|
+
# applied Rubocop rules that fail-close on gems still targeting 3.3.
|
|
31
|
+
TargetRubyVersion: 3.3
|
data/lib/metanorma-utils.rb
CHANGED
data/lib/utils/anchor.rb
CHANGED
|
@@ -105,7 +105,7 @@ module Metanorma
|
|
|
105
105
|
def to_ncname_prep(name, asciionly)
|
|
106
106
|
name = name&.to_s
|
|
107
107
|
name.nil? and name = ""
|
|
108
|
-
asciionly and name =
|
|
108
|
+
asciionly and name = HTML_ENTITIES.encode(name,
|
|
109
109
|
:basic, :hexadecimal)
|
|
110
110
|
[name, name.nil? || name.empty? || name.match?(SAFE_NCNAME_REGEXP)]
|
|
111
111
|
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "asciidoctor"
|
|
4
|
+
|
|
5
|
+
# Asciidoctor 2.0.x accumulates a multi-line table cell by rebuilding the
|
|
6
|
+
# whole cell buffer String on every appended line
|
|
7
|
+
# (`parser_ctx.buffer = %(#{parser_ctx.buffer}#{line}#{LF})` in
|
|
8
|
+
# Asciidoctor::Parser.parse_table), so building a cell that spans N lines
|
|
9
|
+
# is quadratic in the bytes of the cell. Generated tables with cells that
|
|
10
|
+
# span tens of thousands of lines allocate ~10 GB of transient Strings
|
|
11
|
+
# and exceed modest memory caps before the table finishes parsing.
|
|
12
|
+
#
|
|
13
|
+
# The patch below carries the fix inside metanorma-utils: it prepends a
|
|
14
|
+
# corrected Asciidoctor::Parser.parse_table whose appends go through
|
|
15
|
+
# ParserContext#append_to_buffer, and prepends ParserContext methods that
|
|
16
|
+
# accumulate the cell as an Array of segments joined once at close (and
|
|
17
|
+
# whenever the CSV quote check needs the whole record). No String owned
|
|
18
|
+
# by the buffer is ever mutated, so no frozen-string workaround is
|
|
19
|
+
# needed, and the per-line buffer-sized String churn the GC chases is
|
|
20
|
+
# gone.
|
|
21
|
+
#
|
|
22
|
+
# This is also the shape the asciidoctor maintainers have stated they
|
|
23
|
+
# would accept upstream: in-place string mutation is barred there by
|
|
24
|
+
# frozen-string literals and Opal compatibility, and their stated
|
|
25
|
+
# solution is "an array that is later concatenated" (the comment thread
|
|
26
|
+
# of asciidoctor PR #4879). An upstream fix of either stated shape stands
|
|
27
|
+
# the prepend down (see the disarm check at the end of this file).
|
|
28
|
+
module Asciidoctor
|
|
29
|
+
# Replaces the buffer-mutating ParserContext methods; the two stock
|
|
30
|
+
# readers of the buffer (the CSV quote check and close_cell) drain the
|
|
31
|
+
# pending segments into the materialized buffer and delegate upward.
|
|
32
|
+
module TableCellBufferParserContextPatch
|
|
33
|
+
# Appends the String to the buffer of the currently open cell as a
|
|
34
|
+
# pending segment; amortized O(1) per accumulated line.
|
|
35
|
+
def append_to_buffer(str)
|
|
36
|
+
(@buffer_segments ||= []) << str
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def skip_past_delimiter(pre)
|
|
41
|
+
(@buffer_segments ||= []) << pre << @delimiter
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def skip_past_escaped_delimiter(pre)
|
|
46
|
+
(@buffer_segments ||= []) << pre.chop << @delimiter
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def buffer_has_unclosed_quotes?(append = nil, q = '"')
|
|
51
|
+
drain_buffer_segments
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def close_cell(eol = false)
|
|
56
|
+
drain_buffer_segments
|
|
57
|
+
super
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
# Materializes the pending segments onto the buffer String the stock
|
|
63
|
+
# methods read. Cost is proportional to the pending segments only;
|
|
64
|
+
# close_cell drains once per cell, and the quote check drains at most
|
|
65
|
+
# once per check - the same order as the stock check's own copy of
|
|
66
|
+
# the full record ((@buffer + append).strip).
|
|
67
|
+
def drain_buffer_segments
|
|
68
|
+
(@buffer_segments ||= []).empty? and return
|
|
69
|
+
@buffer = @buffer + @buffer_segments.join
|
|
70
|
+
@buffer_segments.clear
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
module TableCellBufferPatch
|
|
76
|
+
def parse_table(table_reader, parent, attributes)
|
|
77
|
+
table = Table.new(parent, attributes)
|
|
78
|
+
|
|
79
|
+
if (attributes.key? 'cols') && !(colspecs = parse_colspecs attributes['cols']).empty?
|
|
80
|
+
table.create_columns colspecs
|
|
81
|
+
explicit_colspecs = true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
skipped = table_reader.skip_blank_lines || 0
|
|
85
|
+
if attributes['header-option']
|
|
86
|
+
table.has_header_option = true
|
|
87
|
+
elsif skipped == 0 && !attributes['noheader-option']
|
|
88
|
+
# NOTE: assume table has header until we know otherwise; if it doesn't (nil), cells in first row get reprocessed
|
|
89
|
+
table.has_header_option = :implicit
|
|
90
|
+
implicit_header = true
|
|
91
|
+
end
|
|
92
|
+
parser_ctx = Table::ParserContext.new table_reader, table, attributes
|
|
93
|
+
format, loop_idx, implicit_header_boundary = parser_ctx.format, -1, nil
|
|
94
|
+
|
|
95
|
+
while (line = table_reader.read_line)
|
|
96
|
+
if (beyond_first = (loop_idx += 1) > 0) && line.empty?
|
|
97
|
+
line = nil
|
|
98
|
+
implicit_header_boundary += 1 if implicit_header_boundary
|
|
99
|
+
elsif format == 'psv'
|
|
100
|
+
if parser_ctx.starts_with_delimiter? line
|
|
101
|
+
line = line.slice 1, line.length
|
|
102
|
+
# push empty cell spec if cell boundary appears at start of line
|
|
103
|
+
parser_ctx.close_open_cell
|
|
104
|
+
implicit_header_boundary = nil if implicit_header_boundary
|
|
105
|
+
else
|
|
106
|
+
next_cellspec, line = parse_cellspec line, :start, parser_ctx.delimiter
|
|
107
|
+
# if cellspec is not nil, we're at a cell boundary
|
|
108
|
+
if next_cellspec
|
|
109
|
+
parser_ctx.close_open_cell next_cellspec
|
|
110
|
+
implicit_header_boundary = nil if implicit_header_boundary
|
|
111
|
+
# otherwise, the cell continues from previous line
|
|
112
|
+
elsif implicit_header_boundary && implicit_header_boundary == loop_idx
|
|
113
|
+
table.has_header_option = implicit_header = implicit_header_boundary = nil
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
unless beyond_first
|
|
119
|
+
table_reader.mark
|
|
120
|
+
# NOTE implicit header is offset by at least one blank line; implicit_header_boundary tracks size of gap
|
|
121
|
+
if implicit_header
|
|
122
|
+
if table_reader.has_more_lines? && table_reader.peek_line.empty?
|
|
123
|
+
implicit_header_boundary = 1
|
|
124
|
+
else
|
|
125
|
+
table.has_header_option = implicit_header = nil
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# this loop is used for flow control; internal logic controls how many times it executes
|
|
131
|
+
while true
|
|
132
|
+
if line && (m = parser_ctx.match_delimiter line)
|
|
133
|
+
pre_match, post_match = m.pre_match, m.post_match
|
|
134
|
+
case format
|
|
135
|
+
when 'csv'
|
|
136
|
+
if parser_ctx.buffer_has_unclosed_quotes? pre_match
|
|
137
|
+
parser_ctx.skip_past_delimiter pre_match
|
|
138
|
+
break if (line = post_match).empty?
|
|
139
|
+
redo
|
|
140
|
+
end
|
|
141
|
+
parser_ctx.append_to_buffer pre_match
|
|
142
|
+
when 'dsv'
|
|
143
|
+
if pre_match.end_with? '\\'
|
|
144
|
+
parser_ctx.skip_past_escaped_delimiter pre_match
|
|
145
|
+
if (line = post_match).empty?
|
|
146
|
+
parser_ctx.append_to_buffer LF
|
|
147
|
+
parser_ctx.keep_cell_open
|
|
148
|
+
break
|
|
149
|
+
end
|
|
150
|
+
redo
|
|
151
|
+
end
|
|
152
|
+
parser_ctx.append_to_buffer pre_match
|
|
153
|
+
else # psv
|
|
154
|
+
if pre_match.end_with? '\\'
|
|
155
|
+
parser_ctx.skip_past_escaped_delimiter pre_match
|
|
156
|
+
if (line = post_match).empty?
|
|
157
|
+
parser_ctx.append_to_buffer LF
|
|
158
|
+
parser_ctx.keep_cell_open
|
|
159
|
+
break
|
|
160
|
+
end
|
|
161
|
+
redo
|
|
162
|
+
end
|
|
163
|
+
next_cellspec, cell_text = parse_cellspec pre_match
|
|
164
|
+
parser_ctx.push_cellspec next_cellspec
|
|
165
|
+
parser_ctx.append_to_buffer cell_text
|
|
166
|
+
end
|
|
167
|
+
# don't break if empty to preserve empty cell found at end of line (see issue #1106)
|
|
168
|
+
line = nil if (line = post_match).empty?
|
|
169
|
+
parser_ctx.close_cell
|
|
170
|
+
else
|
|
171
|
+
# no other delimiters to see here; suck up this line into the buffer and move on
|
|
172
|
+
parser_ctx.append_to_buffer %(#{line}#{LF})
|
|
173
|
+
case format
|
|
174
|
+
when 'csv'
|
|
175
|
+
if parser_ctx.buffer_has_unclosed_quotes?
|
|
176
|
+
table.has_header_option = implicit_header = implicit_header_boundary = nil if implicit_header_boundary && loop_idx == 0
|
|
177
|
+
parser_ctx.keep_cell_open
|
|
178
|
+
else
|
|
179
|
+
parser_ctx.close_cell true
|
|
180
|
+
end
|
|
181
|
+
when 'dsv'
|
|
182
|
+
parser_ctx.close_cell true
|
|
183
|
+
else # psv
|
|
184
|
+
parser_ctx.keep_cell_open
|
|
185
|
+
end
|
|
186
|
+
break
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# NOTE cell may already be closed if table format is csv or dsv
|
|
191
|
+
if parser_ctx.cell_open?
|
|
192
|
+
parser_ctx.close_cell true unless table_reader.has_more_lines?
|
|
193
|
+
else
|
|
194
|
+
table_reader.skip_blank_lines || break
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
parser_ctx.close_table
|
|
199
|
+
table.assign_column_widths unless (table.attributes['colcount'] ||= table.columns.size) == 0 || explicit_colspecs
|
|
200
|
+
table.has_header_option = true if implicit_header
|
|
201
|
+
table.partition_header_footer attributes
|
|
202
|
+
|
|
203
|
+
table
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
class Table
|
|
208
|
+
class ParserContext
|
|
209
|
+
prepend TableCellBufferParserContextPatch
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
module Metanorma
|
|
215
|
+
module Utils
|
|
216
|
+
module AsciidoctorTableBuffer
|
|
217
|
+
# Resolved once, before the prepend: once our parse_table is in
|
|
218
|
+
# place, Parser.method(:parse_table) resolves to the prepended copy
|
|
219
|
+
# and its source_location points at this file, not asciidoctor's.
|
|
220
|
+
PARSER_FILE = ::Asciidoctor::Parser
|
|
221
|
+
.method(:parse_table).source_location.to_a.first
|
|
222
|
+
|
|
223
|
+
class << self
|
|
224
|
+
def apply!
|
|
225
|
+
return false unless defined?(::Asciidoctor::VERSION)
|
|
226
|
+
return true if applied?
|
|
227
|
+
|
|
228
|
+
parser_file = PARSER_FILE
|
|
229
|
+
return false unless parser_file && File.file?(parser_file)
|
|
230
|
+
# Stand down once asciidoctor fixes the quadratic rebuild
|
|
231
|
+
# itself, in either shape: appending in place
|
|
232
|
+
# (append_to_buffer), or accumulating lines in an Array
|
|
233
|
+
# joined at close - the shape the asciidoctor maintainers
|
|
234
|
+
# have stated is acceptable upstream (string mutation is
|
|
235
|
+
# barred there by frozen-string literals and Opal
|
|
236
|
+
# compatibility; see the comment thread of asciidoctor PR
|
|
237
|
+
# #4879). Detecting both matters: this patch prepends a whole
|
|
238
|
+
# copy of parse_table, so shadowing an upstream fix with a
|
|
239
|
+
# stale copy would silently revert their behavior.
|
|
240
|
+
parser_src = File.read(parser_file)
|
|
241
|
+
return false if parser_src.include?("append_to_buffer")
|
|
242
|
+
return false unless parser_src.include?('parser_ctx.buffer = %(#{parser_ctx.buffer')
|
|
243
|
+
|
|
244
|
+
::Asciidoctor::Parser.singleton_class
|
|
245
|
+
.prepend(::Asciidoctor::TableCellBufferPatch)
|
|
246
|
+
::Asciidoctor::Table::ParserContext
|
|
247
|
+
.prepend(::Asciidoctor::TableCellBufferParserContextPatch)
|
|
248
|
+
@applied = true
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def applied?
|
|
252
|
+
@applied ||= false
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
Metanorma::Utils::AsciidoctorTableBuffer.apply!
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Utils
|
|
5
|
+
# Large metanorma compiles churn transients of hundreds of MB per
|
|
6
|
+
# phase (macro rendering, XML cleanup, whole-document assembly) on
|
|
7
|
+
# top of a multi-GB live set. The churn is malloc-side
|
|
8
|
+
# (libxml/moxml/liquid/regexp), so the GC's heap-growth heuristics
|
|
9
|
+
# fire too late under memory caps and the process dies thrashing.
|
|
10
|
+
# Call gc_when_bloated! at phase boundaries: collect whenever
|
|
11
|
+
# uncollected malloc has exceeded the budget.
|
|
12
|
+
module GcBudget
|
|
13
|
+
BUDGET_BYTES = 512 * (1 << 20)
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def gc_when_bloated!
|
|
17
|
+
GC.start if GC.stat(:malloc_increase_bytes) > BUDGET_BYTES
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/utils/main.rb
CHANGED
|
@@ -9,6 +9,12 @@ require_relative "cjk"
|
|
|
9
9
|
|
|
10
10
|
module Metanorma
|
|
11
11
|
module Utils
|
|
12
|
+
# One shared decoder: instantiating HTMLEntities builds a full entity
|
|
13
|
+
# table per instance, and these helpers run per attribute value / per
|
|
14
|
+
# text node in document-scale pipelines — hundreds of thousands of
|
|
15
|
+
# live decoders were observed retained in a single metanorma compile.
|
|
16
|
+
HTML_ENTITIES = HTMLEntities.new
|
|
17
|
+
|
|
12
18
|
class << self
|
|
13
19
|
# , " => ," : CSV definition does not deal with space followed by quote
|
|
14
20
|
# at start of field
|
|
@@ -38,7 +44,7 @@ module Metanorma
|
|
|
38
44
|
|
|
39
45
|
# TODO needs internationalisation of quote
|
|
40
46
|
def smartformat(text)
|
|
41
|
-
ret =
|
|
47
|
+
ret = HTML_ENTITIES.decode(
|
|
42
48
|
text.gsub(/ --? /, " — ")
|
|
43
49
|
.gsub("--", "—"),
|
|
44
50
|
)
|
|
@@ -47,7 +53,7 @@ module Metanorma
|
|
|
47
53
|
ret = ret.smart_format
|
|
48
54
|
ret = ret.gsub(%r{(#{CJK})\u200a}o, "\\1")
|
|
49
55
|
.gsub(%r{\u200a(#{CJK})}o, "\\1")
|
|
50
|
-
|
|
56
|
+
HTML_ENTITIES.encode(ret, :basic)
|
|
51
57
|
end
|
|
52
58
|
|
|
53
59
|
def endash_date(elem)
|
data/lib/utils/version.rb
CHANGED
data/lib/utils/xml.rb
CHANGED
|
@@ -30,17 +30,29 @@ module Metanorma
|
|
|
30
30
|
HERE
|
|
31
31
|
|
|
32
32
|
class << self
|
|
33
|
+
# The parsed NOKOHEAD scaffold, shared by every noko fragment.
|
|
34
|
+
def noko_template
|
|
35
|
+
@noko_template ||= ::Nokogiri::XML.parse(NOKOHEAD)
|
|
36
|
+
end
|
|
37
|
+
|
|
33
38
|
def attr_code(attributes)
|
|
34
39
|
attributes.compact.transform_values do |v|
|
|
35
|
-
v.is_a?(String) ?
|
|
40
|
+
v.is_a?(String) ? HTML_ENTITIES.decode(v) : v
|
|
36
41
|
end
|
|
37
42
|
end
|
|
38
43
|
|
|
39
44
|
# block for processing XML document fragments as XHTML,
|
|
40
45
|
# to allow for HTMLentities
|
|
41
46
|
# Unescape special chars used in Asciidoctor substitution processing
|
|
47
|
+
# The template document is parsed once per process: noko runs for
|
|
48
|
+
# every inline element and block a converter emits (a metanorma
|
|
49
|
+
# plateau compile measured 73k calls), and re-parsing NOKOHEAD
|
|
50
|
+
# minted a fresh HTML-skeleton Document per call — 75k retained
|
|
51
|
+
# documents with their node caches, a top memory consumer of
|
|
52
|
+
# large compiles. Fragments off the shared document are
|
|
53
|
+
# independent trees; conversion is single-threaded.
|
|
42
54
|
def noko(_script = "Latn", &block)
|
|
43
|
-
fragment =
|
|
55
|
+
fragment = noko_template.fragment("")
|
|
44
56
|
::Nokogiri::XML::Builder.with fragment, &block
|
|
45
57
|
fragment
|
|
46
58
|
.to_xml(encoding: "UTF-8", indent: 0,
|
|
@@ -74,7 +86,7 @@ module Metanorma
|
|
|
74
86
|
end
|
|
75
87
|
|
|
76
88
|
def noko_html(&block)
|
|
77
|
-
doc =
|
|
89
|
+
doc = noko_template.dup
|
|
78
90
|
fragment = doc.fragment("")
|
|
79
91
|
::Nokogiri::XML::Builder.with fragment, &block
|
|
80
92
|
fragment.to_xml(encoding: "UTF-8", indent: 0,
|
|
@@ -97,7 +109,7 @@ module Metanorma
|
|
|
97
109
|
end
|
|
98
110
|
|
|
99
111
|
def numeric_escapes(xml)
|
|
100
|
-
c =
|
|
112
|
+
c = HTML_ENTITIES
|
|
101
113
|
xml.split(/(&[^ \r\n\t#&;]+;)/).map do |t|
|
|
102
114
|
if /^(&[^ \t\r\n#;]+;)/.match?(t)
|
|
103
115
|
c.encode(c.decode(t), :hexadecimal)
|
data/metanorma-utils.gemspec
CHANGED
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
f.match(%r{^(test|spec|features|bin|.github)/}) \
|
|
25
25
|
|| f.match(%r{Rakefile|bin/rspec})
|
|
26
26
|
end
|
|
27
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 3.
|
|
27
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.3.0")
|
|
28
28
|
|
|
29
29
|
spec.add_dependency "asciidoctor", ">= 2"
|
|
30
30
|
spec.add_dependency "concurrent-ruby"
|
|
@@ -47,6 +47,6 @@ Gem::Specification.new do |spec|
|
|
|
47
47
|
spec.add_development_dependency "simplecov", "~> 0.15"
|
|
48
48
|
spec.add_development_dependency "timecop", "~> 0.9"
|
|
49
49
|
spec.add_development_dependency "webmock"
|
|
50
|
-
spec.add_development_dependency "canon"
|
|
50
|
+
spec.add_development_dependency "canon"
|
|
51
51
|
# spec.metadata["rubygems_mfa_required"] = "true"
|
|
52
52
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: metanorma-utils
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: asciidoctor
|
|
@@ -294,16 +294,16 @@ dependencies:
|
|
|
294
294
|
name: canon
|
|
295
295
|
requirement: !ruby/object:Gem::Requirement
|
|
296
296
|
requirements:
|
|
297
|
-
- -
|
|
297
|
+
- - ">="
|
|
298
298
|
- !ruby/object:Gem::Version
|
|
299
|
-
version: 0
|
|
299
|
+
version: '0'
|
|
300
300
|
type: :development
|
|
301
301
|
prerelease: false
|
|
302
302
|
version_requirements: !ruby/object:Gem::Requirement
|
|
303
303
|
requirements:
|
|
304
|
-
- -
|
|
304
|
+
- - ">="
|
|
305
305
|
- !ruby/object:Gem::Version
|
|
306
|
-
version: 0
|
|
306
|
+
version: '0'
|
|
307
307
|
description: 'metanorma-utils provides utilities for the Metanorma stack
|
|
308
308
|
|
|
309
309
|
'
|
|
@@ -314,7 +314,6 @@ extensions: []
|
|
|
314
314
|
extra_rdoc_files: []
|
|
315
315
|
files:
|
|
316
316
|
- ".gitignore"
|
|
317
|
-
- ".hound.yml"
|
|
318
317
|
- ".rubocop.yml"
|
|
319
318
|
- Gemfile
|
|
320
319
|
- LICENSE
|
|
@@ -323,7 +322,9 @@ files:
|
|
|
323
322
|
- lib/sterile/sterile.rb
|
|
324
323
|
- lib/utils/anchor.rb
|
|
325
324
|
- lib/utils/anchor_ranges.rb
|
|
325
|
+
- lib/utils/asciidoctor_table_buffer.rb
|
|
326
326
|
- lib/utils/cjk.rb
|
|
327
|
+
- lib/utils/gc_budget.rb
|
|
327
328
|
- lib/utils/hash_transform_keys.rb
|
|
328
329
|
- lib/utils/image.rb
|
|
329
330
|
- lib/utils/linestatus.rb
|
|
@@ -346,7 +347,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
346
347
|
requirements:
|
|
347
348
|
- - ">="
|
|
348
349
|
- !ruby/object:Gem::Version
|
|
349
|
-
version: 3.
|
|
350
|
+
version: 3.3.0
|
|
350
351
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
351
352
|
requirements:
|
|
352
353
|
- - ">="
|