metanorma 2.5.1 → 2.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9167ec4dc502678a508ea35bb8ed81d3914487b0dd2f2562b2e26bd2803faff
4
- data.tar.gz: cfe251c6ed0436df5b7f9eba6c01e368fb0bee55651366a83a949f403f2b10bc
3
+ metadata.gz: cc6ddbece893102c78d8b27a94f175a85c45cfb911e8d7bc908440bd19a58e85
4
+ data.tar.gz: 226289825a52fe8da92a744a00a2239385e46f0964468be522f6b5bb7ba8066d
5
5
  SHA512:
6
- metadata.gz: 5fe17ebf90571ce884b547f38a8736947d749ad9ec45a7dfcc9e8bb1aef91f2b0e2e284d509d70da150cb806c402945555d698c6f06e03006a46500e4648707e
7
- data.tar.gz: 6bf8355bd20b904f7fc7960aadbbbb6363e052b0c7d83689562f0f1fea4811a0785006f24e1766fbf5d354a44424b37f7ea749328229b8e0af925ab90288176f
6
+ metadata.gz: 0d9c775a338fc4b30a27aa53e217037583228a702c61977070f7e76895718b7a1d8e138b26f3053eed4ae9bdfb361a9b4545b62ad4c6e98df5f187eef8209399
7
+ data.tar.gz: f5c41df1386891d848d76576e783335697c80adb75eefda918a990c414ec112844d53e9f94717049873171f9978acaa28e7eed98e04ab11eedcbb11d87119576
@@ -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
- FileUtils.mv(File.join(@outdir, output_fname),
34
- File.join(@outdir, new_output_fname))
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
- if outputs.nil? || outputs[ext].nil?
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)."
@@ -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
- strict = ext == :presentation || isodoc_options[:strict] == true
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
- strict = ext == :presentation || isodoc_options[:strict] == "true"
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
- if strict || err.message.include?("Fatal:")
173
- @errors << err.message
174
- else
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
@@ -1,3 +1,3 @@
1
1
  module Metanorma
2
- VERSION = "2.5.1".freeze
2
+ VERSION = "2.5.2".freeze
3
3
  end
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.1
4
+ version: 2.5.2
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
data/.hound.yml DELETED
@@ -1,5 +0,0 @@
1
- # Auto-generated by Cimas: Do not edit it manually!
2
- # See https://github.com/metanorma/cimas
3
- ruby:
4
- enabled: true
5
- config_file: .rubocop.yml