metanorma 2.5.1 → 2.5.3
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 +9 -0
- data/lib/metanorma/collection/collection.rb +20 -1
- data/lib/metanorma/collection/log.rb +7 -0
- data/lib/metanorma/collection/renderer/filelocation.rb +7 -3
- data/lib/metanorma/collection/renderer/fileprocess.rb +59 -3
- data/lib/metanorma/collection/renderer/renderer.rb +9 -2
- data/lib/metanorma/compile/compile_options.rb +36 -3
- data/lib/metanorma/compile/render.rb +26 -11
- data/lib/metanorma/version.rb +1 -1
- metadata +2 -3
- 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: 4882b98a473b69b9729ddc48af5d8754c0c3cc2e0e64ba9ef06482b7f9671eb0
|
|
4
|
+
data.tar.gz: ccbc44588f0a8610e5d6cff9c73dce8a90d66b8d65678ae9b1cf884a179ab453
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d14656e113f0eac2e715ba4f7d51b5fb57e2029a54cb947f94b47fbe4ce7f0e3ba0334b92474d74c4fc1d5ad699efd61e8e3f5cfbf1850e5ca31a5066753b38
|
|
7
|
+
data.tar.gz: f579166fec3baaae273925ad3fa63e60e85240f468b696948a50cffd435ee5c444f07482373c7605fe3200ba7f82b34da345bb93dbe2c6831d3d8e92b5c94117
|
data/.rubocop.yml
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
# See https://github.com/metanorma/cimas
|
|
3
3
|
inherit_from:
|
|
4
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
|
|
5
14
|
|
|
6
15
|
# Rubocop plugins enabled centrally so every metanorma-org gem picks them up
|
|
7
16
|
# on cimas sync — best practice belongs at the shared-template layer, not
|
|
@@ -11,6 +11,9 @@ module Metanorma
|
|
|
11
11
|
|
|
12
12
|
class AdocFileNotFoundException < StandardError; end
|
|
13
13
|
|
|
14
|
+
# One or more collection members failed to render (#586)
|
|
15
|
+
class RenderFailureException < StandardError; end
|
|
16
|
+
|
|
14
17
|
# Metanorma collection of documents
|
|
15
18
|
class Collection
|
|
16
19
|
attr_reader :file, :prefatory, :final
|
|
@@ -132,8 +135,24 @@ module Metanorma
|
|
|
132
135
|
opts[:log] = @log
|
|
133
136
|
opts[:flavor] = @flavor
|
|
134
137
|
output_folder(opts)
|
|
135
|
-
::Metanorma::Collection::Renderer.render self, opts
|
|
138
|
+
cr = ::Metanorma::Collection::Renderer.render self, opts
|
|
136
139
|
clean_exit
|
|
140
|
+
render_failures_abort(cr)
|
|
141
|
+
cr
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# A member whose rendering fails no longer lets the collection
|
|
145
|
+
# report success with the document missing from the output (#586).
|
|
146
|
+
# Every member is rendered before aborting, so one run reports all
|
|
147
|
+
# failures; the raise makes callers (CLI, suma) exit non-zero. Runs
|
|
148
|
+
# after clean_exit so the failures are in the written log.
|
|
149
|
+
def render_failures_abort(renderer)
|
|
150
|
+
f = renderer.fatal_errors
|
|
151
|
+
f.empty? and return
|
|
152
|
+
raise RenderFailureException.new(
|
|
153
|
+
"Collection render failed for #{f.size} document(s):\n" +
|
|
154
|
+
f.join("\n"),
|
|
155
|
+
)
|
|
137
156
|
end
|
|
138
157
|
|
|
139
158
|
def output_folder(opts)
|
|
@@ -11,6 +11,13 @@ module Metanorma
|
|
|
11
11
|
"METANORMA_3": { category: "Cross-References",
|
|
12
12
|
error: "<strong>** Unresolved reference to document %s from eref</strong>",
|
|
13
13
|
severity: 2 },
|
|
14
|
+
"METANORMA_4": { category: "Rendering",
|
|
15
|
+
error: "Collection member %s: expected output " \
|
|
16
|
+
"file not generated: %s",
|
|
17
|
+
severity: 0 },
|
|
18
|
+
"METANORMA_5": { category: "Rendering",
|
|
19
|
+
error: "Collection member %s: rendering error: %s",
|
|
20
|
+
severity: 0 },
|
|
14
21
|
}.freeze
|
|
15
22
|
# rubocop:enable Naming/VariableNumber
|
|
16
23
|
end
|
|
@@ -26,12 +26,16 @@ module Metanorma
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# Move file to new output filename if specified
|
|
29
|
+
# Move file to new output filename if specified. A member that
|
|
30
|
+
# failed to render has nothing to move: keep the original name
|
|
31
|
+
# registered so output verification reports it as missing, rather
|
|
32
|
+
# than crashing the whole collection on the mv (#586).
|
|
30
33
|
def handle_new_output_filename(output_fname, new_output_fname)
|
|
31
34
|
return output_fname unless new_output_fname
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
src = File.join(@outdir, output_fname)
|
|
37
|
+
File.exist?(src) or return output_fname
|
|
38
|
+
FileUtils.mv(src, File.join(@outdir, new_output_fname))
|
|
35
39
|
new_output_fname
|
|
36
40
|
end
|
|
37
41
|
|
|
@@ -18,9 +18,61 @@ module Metanorma
|
|
|
18
18
|
opts = compile_options_base(identifier)
|
|
19
19
|
.merge(compile_options_update(identifier))
|
|
20
20
|
|
|
21
|
+
err_count = @compile.errors.size
|
|
21
22
|
@compile.compile file, opts
|
|
22
23
|
@files.set(identifier, :outputs, {})
|
|
23
24
|
file_compile_formats(filename, identifier, opts)
|
|
25
|
+
file_compile_verify(identifier, @compile.errors[err_count..] || [])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# A member whose rendering raised produces no output file, but the
|
|
29
|
+
# collection build used to continue, exit 0, and link the absent
|
|
30
|
+
# document from index.html -- silent data loss (#586). Two
|
|
31
|
+
# complementary checks close that hole. (1) Compile pools rescued
|
|
32
|
+
# error messages in @compile.errors for its caller to surface; the
|
|
33
|
+
# standalone CLI does, but the collection pipeline never read the
|
|
34
|
+
# pool. The pool is shared across members and never reset, so the
|
|
35
|
+
# slice appended during this member's compile is this member's
|
|
36
|
+
# errors -- sound while members compile sequentially (the current
|
|
37
|
+
# design; @compile's own format-level parallelism is joined before
|
|
38
|
+
# compile returns). (2) Independently of whether anything was
|
|
39
|
+
# pooled, verify the outputs registered for the member exist on
|
|
40
|
+
# disk. Failures are reported here and accumulated; the collection
|
|
41
|
+
# renders every remaining member before Collection#render aborts,
|
|
42
|
+
# so one run reports all failed members.
|
|
43
|
+
def file_compile_verify(identifier, errors)
|
|
44
|
+
# Section-split parts (marked with :parentid) register format
|
|
45
|
+
# slots they never produce -- their :xml slot in particular is
|
|
46
|
+
# never written, and under filename renaming it aliases to the
|
|
47
|
+
# parent's name -- so verifying them is a false positive. #586
|
|
48
|
+
# was scoped to the non-sectionsplit path; parts are excluded
|
|
49
|
+
# like their parents (which return early above).
|
|
50
|
+
@files.get(identifier, :parentid) and return
|
|
51
|
+
missing = (@files.get(identifier, :outputs) || {})
|
|
52
|
+
.reject { |_fmt, path| File.exist?(path) }
|
|
53
|
+
errors.empty? && missing.empty? and return
|
|
54
|
+
file_compile_failure_log(identifier, errors, missing)
|
|
55
|
+
msg = file_compile_failure_msg(identifier, errors, missing)
|
|
56
|
+
::Metanorma::Util.log("[metanorma] Error: #{msg}", :error)
|
|
57
|
+
fatal_errors << msg
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def file_compile_failure_log(identifier, errors, missing)
|
|
61
|
+
missing.each_value do |path|
|
|
62
|
+
@log&.add("METANORMA_4", nil, params: [identifier, path])
|
|
63
|
+
end
|
|
64
|
+
errors.each do |e|
|
|
65
|
+
@log&.add("METANORMA_5", nil, params: [identifier, e])
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def file_compile_failure_msg(identifier, errors, missing)
|
|
70
|
+
ret = []
|
|
71
|
+
missing.empty? or
|
|
72
|
+
ret << "expected output not generated: " \
|
|
73
|
+
"#{missing.values.join(', ')}"
|
|
74
|
+
ret += errors
|
|
75
|
+
"Failed to render collection member #{identifier}: #{ret.join('; ')}"
|
|
24
76
|
end
|
|
25
77
|
|
|
26
78
|
def compile_options_base(identifier)
|
|
@@ -198,7 +250,7 @@ module Metanorma
|
|
|
198
250
|
end
|
|
199
251
|
|
|
200
252
|
def update_bibitem(bib, identifier)
|
|
201
|
-
newbib, url = update_bibitem_prep(bib, identifier)
|
|
253
|
+
newbib, url, attachment = update_bibitem_prep(bib, identifier)
|
|
202
254
|
newbib or return
|
|
203
255
|
dest = begin
|
|
204
256
|
newbib.at("./docidentifier") || newbib.at(ns("./docidentifier"))
|
|
@@ -206,18 +258,22 @@ module Metanorma
|
|
|
206
258
|
nil
|
|
207
259
|
end
|
|
208
260
|
dest or dest = newbib.elements[-1]
|
|
261
|
+
# An attachment carries both a <uri type="attachment"> (which drives
|
|
262
|
+
# isodoc's fmt-link attachment="true") and the citation uri; the
|
|
263
|
+
# attachment uri goes first. metanorma/iso-10303#208.
|
|
264
|
+
attachment and dest.previous = "<uri type='attachment'>#{url}</uri>"
|
|
209
265
|
dest.previous = "<uri type='citation'>#{url}</uri>"
|
|
210
266
|
bib.replace(newbib)
|
|
211
267
|
end
|
|
212
268
|
|
|
213
269
|
def update_bibitem_prep(bib, identifier)
|
|
214
|
-
docid = get_bibitem_docid(bib, identifier) or return [nil, nil]
|
|
270
|
+
docid = get_bibitem_docid(bib, identifier) or return [nil, nil, nil]
|
|
215
271
|
newbib = dup_bibitem(docid, bib)
|
|
216
272
|
attachment = @files.get(docid, :attachment)
|
|
217
273
|
url = @files.url(docid, relative: true, doc: !attachment)
|
|
218
274
|
current_html = referencing_html_location(identifier, attachment)
|
|
219
275
|
url = make_relative_path(current_html, url) if current_html
|
|
220
|
-
[newbib, url]
|
|
276
|
+
[newbib, url, attachment]
|
|
221
277
|
end
|
|
222
278
|
|
|
223
279
|
# Location of the HTML that will carry the cross-reference to the target,
|
|
@@ -19,7 +19,7 @@ module Metanorma
|
|
|
19
19
|
|
|
20
20
|
attr_accessor :isodoc, :isodoc_presxml
|
|
21
21
|
attr_reader :xml, :compile, :compile_options, :documents, :outdir,
|
|
22
|
-
:manifest
|
|
22
|
+
:manifest, :fatal_errors
|
|
23
23
|
|
|
24
24
|
# Run the block with the renderer in nested mode, restoring the previous
|
|
25
25
|
# mode afterwards. Nested mode (used by sectionsplit, which re-enters this
|
|
@@ -93,6 +93,10 @@ module Metanorma
|
|
|
93
93
|
@final = collection.final
|
|
94
94
|
@c = HTMLEntities.new
|
|
95
95
|
@files_to_delete = []
|
|
96
|
+
# Per-member rendering failures (missing outputs, pooled compile
|
|
97
|
+
# errors), accumulated by file_compile_verify; Collection#render
|
|
98
|
+
# aborts on them once every member has rendered (#586).
|
|
99
|
+
@fatal_errors = []
|
|
96
100
|
@nested = options[:nested]
|
|
97
101
|
# if false, this is the root instance of Renderer
|
|
98
102
|
# if true, then this is not the last time Renderer will be run
|
|
@@ -302,7 +306,10 @@ module Metanorma
|
|
|
302
306
|
# (Sectionsplit parents get a :presentation output upstream, so they
|
|
303
307
|
# inline whole for the PDF/presentation path.)
|
|
304
308
|
outputs = @files.get(id, :outputs)
|
|
305
|
-
|
|
309
|
+
# The registered path may exist without the file: a member whose
|
|
310
|
+
# rendering failed registers its intended outputs but writes
|
|
311
|
+
# nothing (#586), so check the disk, not just the registration.
|
|
312
|
+
if outputs.nil? || outputs[ext].nil? || !File.exist?(outputs[ext])
|
|
306
313
|
warn "[metanorma] collection document '#{id}' has no compiled " \
|
|
307
314
|
"#{ext} output to inline; skipping (its doc-container would " \
|
|
308
315
|
"otherwise be bibdata-only)."
|
|
@@ -62,10 +62,39 @@ module Metanorma
|
|
|
62
62
|
options_in_file[:sourcecode])
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# Document-model transformer specs contributed by the active taste
|
|
66
|
+
# (+options[:supplied_type]+), or +{}+. Lets a taste (e.g. OIML) make its
|
|
67
|
+
# own output format selectable and choose its presentation/semantic leg,
|
|
68
|
+
# via the taste-aware metanorma-core Processor (metanorma-core#12).
|
|
69
|
+
def taste_transformers(options)
|
|
70
|
+
taste = options[:supplied_type]
|
|
71
|
+
return {} unless taste && defined?(Metanorma::TasteRegister) &&
|
|
72
|
+
Metanorma::TasteRegister.respond_to?(:document_transformers_for)
|
|
73
|
+
|
|
74
|
+
Metanorma::TasteRegister.document_transformers_for(taste) || {}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The processor's output formats merged with the suffixes of any
|
|
78
|
+
# taste-contributed document-model formats, so those formats pass
|
|
79
|
+
# extension validation and get an output suffix.
|
|
80
|
+
def effective_output_formats(options)
|
|
81
|
+
@processor.output_formats.merge(
|
|
82
|
+
taste_transformers(options)
|
|
83
|
+
.transform_values { |spec| spec[:suffix] }.compact,
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Whether +ext+ is generated from presentation XML, honouring both the
|
|
88
|
+
# processor's own answer and a taste transformer's +:presentation+ flag.
|
|
89
|
+
def uses_presentation_xml?(ext, options)
|
|
90
|
+
@processor.use_presentation_xml(ext) ||
|
|
91
|
+
(taste_transformers(options)[ext] || {})[:presentation] == true
|
|
92
|
+
end
|
|
93
|
+
|
|
65
94
|
def get_extensions(options)
|
|
66
95
|
ext = extract_extensions(options)
|
|
67
96
|
!ext.include?(:presentation) && ext.any? do |e|
|
|
68
|
-
|
|
97
|
+
uses_presentation_xml?(e, options)
|
|
69
98
|
end and ext << :presentation
|
|
70
99
|
!ext.include?(:rxl) && options[:site_generate] and
|
|
71
100
|
ext << :rxl
|
|
@@ -73,10 +102,11 @@ module Metanorma
|
|
|
73
102
|
end
|
|
74
103
|
|
|
75
104
|
def extract_extensions(options)
|
|
105
|
+
formats = effective_output_formats(options)
|
|
76
106
|
options[:extension_keys] ||=
|
|
77
|
-
|
|
107
|
+
formats.reduce([]) { |memo, (k, _)| memo << k }
|
|
78
108
|
options[:extension_keys].reduce([]) do |memo, e|
|
|
79
|
-
if
|
|
109
|
+
if formats[e] then memo << e
|
|
80
110
|
else
|
|
81
111
|
unsupported_format_error(e)
|
|
82
112
|
memo
|
|
@@ -123,6 +153,9 @@ module Metanorma
|
|
|
123
153
|
def copy_isodoc_options_attrs(options, ret)
|
|
124
154
|
ret[:datauriimage] = true if options[:datauriimage]
|
|
125
155
|
ret[:sourcefilename] = options[:filename]
|
|
156
|
+
# Carry the active taste through to Processor#output, so its taste-aware
|
|
157
|
+
# document_transformers resolve there (metanorma-core#12).
|
|
158
|
+
ret[:supplied_type] = options[:supplied_type]
|
|
126
159
|
%i(bare sectionsplit sectionsplit_filename install_fonts baseassetpath
|
|
127
160
|
aligncrosselements tocfigures toctables tocrecommendations tocexamples
|
|
128
161
|
strict fonts)
|
|
@@ -67,7 +67,7 @@ module Metanorma
|
|
|
67
67
|
# Process a single extension (output format)
|
|
68
68
|
def process_ext(ext, source_file, semantic_xml, bibdata, output_paths,
|
|
69
69
|
options)
|
|
70
|
-
output_paths[:ext] =
|
|
70
|
+
output_paths[:ext] = effective_output_formats(options)[ext]
|
|
71
71
|
output_paths[:out] = @output_filename.for_format(ext) ||
|
|
72
72
|
output_paths[:xml].sub(/\.[^.]+$/, ".#{output_paths[:ext]}")
|
|
73
73
|
isodoc_options = get_isodoc_options(source_file, options, ext)
|
|
@@ -78,7 +78,7 @@ module Metanorma
|
|
|
78
78
|
)
|
|
79
79
|
|
|
80
80
|
# Otherwise, determine if it uses presentation XML
|
|
81
|
-
if
|
|
81
|
+
if uses_presentation_xml?(ext, options)
|
|
82
82
|
# Format requires presentation XML first, then convert to final format
|
|
83
83
|
process_via_presentation_xml(ext, output_paths, options, isodoc_options)
|
|
84
84
|
else
|
|
@@ -135,8 +135,7 @@ module Metanorma
|
|
|
135
135
|
output_paths[:out], ext, isodoc_options)
|
|
136
136
|
wrap_html(options, output_paths[:ext], output_paths[:out])
|
|
137
137
|
rescue StandardError => e
|
|
138
|
-
|
|
139
|
-
isodoc_error_process(e, strict, false)
|
|
138
|
+
isodoc_error_process(e, strict_error?(ext, isodoc_options), false)
|
|
140
139
|
end
|
|
141
140
|
|
|
142
141
|
# Process format directly from semantic XML
|
|
@@ -145,8 +144,7 @@ module Metanorma
|
|
|
145
144
|
ext, isodoc_options)
|
|
146
145
|
true # Return as Thread equivalent
|
|
147
146
|
rescue StandardError => e
|
|
148
|
-
|
|
149
|
-
isodoc_error_process(e, strict, true)
|
|
147
|
+
isodoc_error_process(e, strict_error?(ext, isodoc_options), true)
|
|
150
148
|
ext != :presentation
|
|
151
149
|
end
|
|
152
150
|
|
|
@@ -168,12 +166,29 @@ module Metanorma
|
|
|
168
166
|
|
|
169
167
|
private
|
|
170
168
|
|
|
169
|
+
# A presentation failure is always strict: every downstream format
|
|
170
|
+
# needs the presentation XML. The :strict option arrives as a
|
|
171
|
+
# boolean from the CLI but as a string from document attributes, so
|
|
172
|
+
# accept both (the two rescue sites previously each checked only
|
|
173
|
+
# one form).
|
|
174
|
+
def strict_error?(ext, isodoc_options)
|
|
175
|
+
ext == :presentation ||
|
|
176
|
+
[true, "true"].include?(isodoc_options[:strict])
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# On the strict path the message is pooled into @errors for the
|
|
180
|
+
# caller to surface (the standalone CLI reports the pool and
|
|
181
|
+
# aborts; the collection renderer attributes it to the failed
|
|
182
|
+
# member, #586). The class+message line is also printed here in all
|
|
183
|
+
# cases, so it sits adjacent to its backtrace in the build log: a
|
|
184
|
+
# pooled message surfaces only at end of run, and a bare backtrace
|
|
185
|
+
# mid-log is undiagnosable. The exception class is retained because
|
|
186
|
+
# for errors like Encoding::UndefinedConversionError it carries
|
|
187
|
+
# half the diagnosis.
|
|
171
188
|
def isodoc_error_process(err, strict, must_abort)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
puts err.message
|
|
176
|
-
end
|
|
189
|
+
msg = "#{err.class}: #{err.message}"
|
|
190
|
+
strict || err.message.include?("Fatal:") and @errors << msg
|
|
191
|
+
puts msg
|
|
177
192
|
puts err.backtrace.join("\n")
|
|
178
193
|
must_abort and 1
|
|
179
194
|
end
|
data/lib/metanorma/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: metanorma
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.5.
|
|
4
|
+
version: 2.5.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -217,7 +217,6 @@ extra_rdoc_files:
|
|
|
217
217
|
files:
|
|
218
218
|
- ".envrc"
|
|
219
219
|
- ".gitignore"
|
|
220
|
-
- ".hound.yml"
|
|
221
220
|
- ".rspec"
|
|
222
221
|
- ".rubocop.yml"
|
|
223
222
|
- CHANGELOG.adoc
|
|
@@ -292,7 +291,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
292
291
|
- !ruby/object:Gem::Version
|
|
293
292
|
version: '0'
|
|
294
293
|
requirements: []
|
|
295
|
-
rubygems_version: 4.0.
|
|
294
|
+
rubygems_version: 4.0.16
|
|
296
295
|
specification_version: 4
|
|
297
296
|
summary: Metanorma is the standard of standards; the metanorma gem allows you to create
|
|
298
297
|
any standard document type supported by Metanorma.
|