metanorma 2.4.3 → 2.5.1
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 +13 -1
- data/lib/metanorma/collection/artifact_store.rb +142 -0
- data/lib/metanorma/collection/filelookup/filelookup.rb +7 -21
- data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +9 -14
- data/lib/metanorma/collection/filelookup/utils.rb +7 -48
- data/lib/metanorma/collection/manifest/manifest.rb +6 -0
- data/lib/metanorma/collection/renderer/fileparse.rb +32 -5
- data/lib/metanorma/collection/renderer/fileprocess.rb +74 -13
- data/lib/metanorma/collection/renderer/renderer.rb +67 -19
- data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +41 -10
- data/lib/metanorma/collection/util/util.rb +16 -0
- data/lib/metanorma/version.rb +1 -1
- data/metanorma.gemspec +1 -1
- data/plans/collection-rendering-architecture-review.md +516 -0
- metadata +7 -9
- data/lib/metanorma/collection/filelookup/base.rb +0 -43
|
@@ -17,10 +17,36 @@ module Metanorma
|
|
|
17
17
|
class Renderer
|
|
18
18
|
FORMATS = %i[html xml doc pdf pdf-portfolio].freeze
|
|
19
19
|
|
|
20
|
-
attr_accessor :isodoc, :isodoc_presxml
|
|
20
|
+
attr_accessor :isodoc, :isodoc_presxml
|
|
21
21
|
attr_reader :xml, :compile, :compile_options, :documents, :outdir,
|
|
22
22
|
:manifest
|
|
23
23
|
|
|
24
|
+
# Run the block with the renderer in nested mode, restoring the previous
|
|
25
|
+
# mode afterwards. Nested mode (used by sectionsplit, which re-enters this
|
|
26
|
+
# renderer's update_xrefs on the pre-split document) preserves unresolved
|
|
27
|
+
# erefs and skips the finalising reference passes.
|
|
28
|
+
def with_nested
|
|
29
|
+
saved = @nested
|
|
30
|
+
@nested = true
|
|
31
|
+
yield
|
|
32
|
+
ensure
|
|
33
|
+
@nested = saved
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Run the block with the renderer preserving unresolved cross-document
|
|
37
|
+
# references as stubs instead of stripping them -- for an isolated build of
|
|
38
|
+
# a collection member (compiled without the rest of its collection
|
|
39
|
+
# present). Unlike +with_nested+, the ordinary intra-document passes still
|
|
40
|
+
# run (xref_process, svgmap); only the cross-document resolve/strip is
|
|
41
|
+
# turned into preserve, so a later reinflation pass can relink the stubs.
|
|
42
|
+
def with_preserve_unresolved
|
|
43
|
+
saved = @preserve_unresolved
|
|
44
|
+
@preserve_unresolved = true
|
|
45
|
+
yield
|
|
46
|
+
ensure
|
|
47
|
+
@preserve_unresolved = saved
|
|
48
|
+
end
|
|
49
|
+
|
|
24
50
|
# This is only going to render the HTML collection
|
|
25
51
|
# @param xml [Metanorma::Collection] input XML collection
|
|
26
52
|
# @param folder [String] input folder
|
|
@@ -71,6 +97,31 @@ module Metanorma
|
|
|
71
97
|
# if false, this is the root instance of Renderer
|
|
72
98
|
# if true, then this is not the last time Renderer will be run
|
|
73
99
|
# (e.g. this is sectionsplit)
|
|
100
|
+
# Isolated/incremental build: preserve unresolved cross-document
|
|
101
|
+
# references as stubs instead of resolving or stripping them, so each
|
|
102
|
+
# member compiles independently and a later reinflation pass relinks.
|
|
103
|
+
@preserve_unresolved = options[:preserve_unresolved]
|
|
104
|
+
# Durable content-addressed store for staged artefacts. Opt-in: a plain
|
|
105
|
+
# build sets neither :preserve_unresolved nor :artifact_store_dir, so it
|
|
106
|
+
# gets the no-op Null store and is unaffected. Staging (:preserve_unresolved)
|
|
107
|
+
# defaults the store directory to ArtifactStore::DEFAULT_DIRNAME when no
|
|
108
|
+
# :artifact_store_dir is given, so callers need not name it. (Reinflation
|
|
109
|
+
# reads stored stubs via the manifest, not this object, so it does not
|
|
110
|
+
# trigger the default -- that would only create an empty directory.)
|
|
111
|
+
@artifact_store =
|
|
112
|
+
if (dir = options[:artifact_store_dir]) || options[:preserve_unresolved]
|
|
113
|
+
ArtifactStore.new(dir || ArtifactStore::DEFAULT_DIRNAME)
|
|
114
|
+
else
|
|
115
|
+
NullArtifactStore.new
|
|
116
|
+
end
|
|
117
|
+
# Reinflation pass: the input is a stored stub-bearing Semantic XML that
|
|
118
|
+
# already has intra-doc xrefs and the document suffix applied, so run
|
|
119
|
+
# only the cross-document resolution, not the passes already done.
|
|
120
|
+
@reinflate = options[:reinflate]
|
|
121
|
+
# Cache retention: by default keep only each document's latest artefacts,
|
|
122
|
+
# pruning superseded content-hash versions on write; keep_cache retains
|
|
123
|
+
# every version (history / multiple states side by side).
|
|
124
|
+
@keep_cache = options[:keep_cache]
|
|
74
125
|
|
|
75
126
|
@coverpage = options[:coverpage] || collection.coverpage
|
|
76
127
|
@coverpage_pdf_portflio = options[:coverpage_pdf_portfolio] ||
|
|
@@ -90,21 +141,6 @@ module Metanorma
|
|
|
90
141
|
directives_normalise_keystore_pdf_portfolio(directives)
|
|
91
142
|
end
|
|
92
143
|
|
|
93
|
-
# KILL
|
|
94
|
-
def directives_normalise_coverpage_pdf_portfolio(directives)
|
|
95
|
-
directives.reject! { |d| d.key == "coverpage-pdf-portfolio" }
|
|
96
|
-
absolute_coverpage_pdf_portflio = @coverpage_pdf_portflio &&
|
|
97
|
-
Pathname.new(@coverpage_pdf_portflio).absolute?
|
|
98
|
-
@coverpage_pdf_portflio =
|
|
99
|
-
Util::rel_path_resolve(@dirname, @coverpage_pdf_portflio)
|
|
100
|
-
absolute_coverpage_pdf_portflio or
|
|
101
|
-
@coverpage_pdf_portflio = Pathname.new(@coverpage_pdf_portflio)
|
|
102
|
-
.relative_path_from(Pathname.new(@outdir)).to_s
|
|
103
|
-
directives << ::Metanorma::Collection::Config::Directive
|
|
104
|
-
.new(key: "coverpage-pdf-portfolio", value: @coverpage_pdf_portflio)
|
|
105
|
-
directives
|
|
106
|
-
end
|
|
107
|
-
|
|
108
144
|
def directives_normalise_coverpage_pdf_portfolio(directives)
|
|
109
145
|
directives_resolve_filepath(directives, "coverpage-pdf-portfolio",
|
|
110
146
|
@coverpage_pdf_portflio)
|
|
@@ -258,10 +294,22 @@ module Metanorma
|
|
|
258
294
|
.new(key: "documents-inline")
|
|
259
295
|
out.bibdatas.each_key do |ident|
|
|
260
296
|
id = @isodoc.docid_prefix(nil, ident.dup)
|
|
261
|
-
@files.get(id, :attachment)
|
|
297
|
+
@files.get(id, :attachment) and next
|
|
298
|
+
# A non-attachment document with no compiled output for THIS format
|
|
299
|
+
# cannot be inlined: leaving it in place serialises as a bibdata-only,
|
|
300
|
+
# namespace-less <metanorma> that mn2pdf silently drops. Skip loudly
|
|
301
|
+
# rather than crash on a nil path or corrupt the collection PDF.
|
|
302
|
+
# (Sectionsplit parents get a :presentation output upstream, so they
|
|
303
|
+
# inline whole for the PDF/presentation path.)
|
|
304
|
+
outputs = @files.get(id, :outputs)
|
|
305
|
+
if outputs.nil? || outputs[ext].nil?
|
|
306
|
+
warn "[metanorma] collection document '#{id}' has no compiled " \
|
|
307
|
+
"#{ext} output to inline; skipping (its doc-container would " \
|
|
308
|
+
"otherwise be bibdata-only)."
|
|
309
|
+
next
|
|
310
|
+
end
|
|
262
311
|
out.documents[Util::key id] =
|
|
263
|
-
Metanorma::Collection::Document
|
|
264
|
-
.raw_file(@files.get(id, :outputs)[ext])
|
|
312
|
+
Metanorma::Collection::Document.raw_file(outputs[ext])
|
|
265
313
|
end
|
|
266
314
|
out
|
|
267
315
|
end
|
|
@@ -7,8 +7,24 @@ require "concurrent-ruby"
|
|
|
7
7
|
|
|
8
8
|
module Metanorma
|
|
9
9
|
class Collection
|
|
10
|
+
# Sectionsplit is a collection render nested inside a collection render:
|
|
11
|
+
# a document is split into one output file per section, and those files are
|
|
12
|
+
# then rendered as their own (sub-)collection.
|
|
13
|
+
#
|
|
14
|
+
# Two output-location invariants matter (both load-bearing -- see the
|
|
15
|
+
# collection architecture review plan and metanorma/iso-10303#208):
|
|
16
|
+
#
|
|
17
|
+
# * XML section files are always written FLAT to the split directory
|
|
18
|
+
# (basename only). HTML output may instead carry a directory taken from
|
|
19
|
+
# sectionsplit_filename, reattached only at HTML compile time
|
|
20
|
+
# (preserve_directory_structure?).
|
|
21
|
+
# * A sectionsplit document's content is emitted at the sectionsplit output
|
|
22
|
+
# location (the collection root, unless sectionsplit_filename sets a
|
|
23
|
+
# directory), NOT at the document's own :out_path. Anything relativising a
|
|
24
|
+
# URL for such a document (e.g. attachment/citation links) must base it on
|
|
25
|
+
# the split location, or the ../../ overshoots.
|
|
10
26
|
class Sectionsplit
|
|
11
|
-
attr_accessor :filecache, :key
|
|
27
|
+
attr_accessor :filecache, :key, :whole_presentation_file
|
|
12
28
|
|
|
13
29
|
def initialize(opts)
|
|
14
30
|
@input_filename = opts[:input]
|
|
@@ -40,6 +56,13 @@ module Metanorma
|
|
|
40
56
|
# Input XML is Semantic XML
|
|
41
57
|
def sectionsplit
|
|
42
58
|
xml = sectionsplit_prep(File.read(@input_filename), @base, @dir)
|
|
59
|
+
# Retain the whole (un-split) Presentation XML. sectionsplit is an
|
|
60
|
+
# HTML-only mechanism, but the collection PDF/presentation path must
|
|
61
|
+
# inline each document intact; without this the sectionsplit parent
|
|
62
|
+
# reaches concatenation with no presentation output and degrades to a
|
|
63
|
+
# bibdata-only, namespace-less <metanorma> stub that mn2pdf drops.
|
|
64
|
+
# https://github.com/metanorma/iso-10303/issues/208
|
|
65
|
+
@whole_presentation_file = write_whole_presentation(xml)
|
|
43
66
|
@key = Metanorma::Collection::XrefProcess::xref_preprocess(xml, @isodoc)
|
|
44
67
|
empty = empty_doc(xml)
|
|
45
68
|
empty1 = empty_attachments(empty)
|
|
@@ -66,11 +89,11 @@ module Metanorma
|
|
|
66
89
|
|
|
67
90
|
def sectionsplit2(xml, empty, chunks, parentnode, opt)
|
|
68
91
|
@pool.post do
|
|
69
|
-
output_filename =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
92
|
+
output_filename = Util.substitute_filename_pattern(
|
|
93
|
+
@sectionsplit_filename,
|
|
94
|
+
document_num: @parent_idx, basename: File.basename(@base, ".*"),
|
|
95
|
+
basename_legacy: @base, sectionsplit_num: opt[:idx]
|
|
96
|
+
)
|
|
74
97
|
warn "Sectionsplit: #{output_filename}"
|
|
75
98
|
a = sectionfile(xml, empty, output_filename, chunks,
|
|
76
99
|
parentnode)
|
|
@@ -108,6 +131,16 @@ module Metanorma
|
|
|
108
131
|
r
|
|
109
132
|
end
|
|
110
133
|
|
|
134
|
+
# Persist the whole, svgmap-restored Presentation XML (the in-memory doc,
|
|
135
|
+
# not the on-disk tmp which still carries the internal svgmap1 marker) so
|
|
136
|
+
# the collection PDF path can inline the document whole. Written flat to
|
|
137
|
+
# the split output directory, where the per-section files also live.
|
|
138
|
+
def write_whole_presentation(xml)
|
|
139
|
+
fname = File.join(@splitdir, "#{@base}.whole.presentation.xml")
|
|
140
|
+
File.write(fname, xml.to_xml, encoding: "UTF-8")
|
|
141
|
+
fname
|
|
142
|
+
end
|
|
143
|
+
|
|
111
144
|
def sectionsplit_preprocess_semxml(file, filename)
|
|
112
145
|
xml = Nokogiri::XML(file, &:huge)
|
|
113
146
|
type = xml.root["flavor"]
|
|
@@ -120,10 +153,8 @@ module Metanorma
|
|
|
120
153
|
|
|
121
154
|
def sectionsplit_update_xrefs(xml)
|
|
122
155
|
if c = @fileslookup&.parent
|
|
123
|
-
|
|
124
|
-
c.
|
|
125
|
-
c.update_xrefs(xml, @ident, {})
|
|
126
|
-
c.nested = n
|
|
156
|
+
# nested mode so unresolved erefs are not deleted
|
|
157
|
+
c.with_nested { c.update_xrefs(xml, @ident, {}) }
|
|
127
158
|
xml.xpath("//xmlns:svgmap").each { |x| x.name = "svgmap1" }
|
|
128
159
|
# do not process svgmap until after files are split
|
|
129
160
|
end
|
|
@@ -81,6 +81,22 @@ module Metanorma
|
|
|
81
81
|
@c.decode(ident).gsub(/(\p{Zs})+/, " ")
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
+
# Substitute special strings in filename patterns. Single source for
|
|
85
|
+
# both FileLookup#substitute_filename_pattern and sectionsplit.
|
|
86
|
+
# @param pattern [String] filename pattern with placeholders
|
|
87
|
+
# @param options [Hash] substitution values:
|
|
88
|
+
# :document_num, :basename, :basename_legacy, :sectionsplit_num
|
|
89
|
+
def substitute_filename_pattern(pattern, options = {})
|
|
90
|
+
pattern or return pattern
|
|
91
|
+
subs = { "{document-num}" => options[:document_num],
|
|
92
|
+
"{basename}" => options[:basename],
|
|
93
|
+
"{basename_legacy}" => options[:basename_legacy],
|
|
94
|
+
"{sectionsplit-num}" => options[:sectionsplit_num] }
|
|
95
|
+
subs.each_with_object(pattern.dup) do |(token, val), result|
|
|
96
|
+
val and result.gsub!(token, val.to_s)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
84
100
|
def taste2flavor(taste)
|
|
85
101
|
tastes = Metanorma::TasteRegister.instance.aliases
|
|
86
102
|
tastes[taste.to_sym] and taste = tastes[taste.to_sym]
|
data/lib/metanorma/version.rb
CHANGED
data/metanorma.gemspec
CHANGED
|
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
spec.bindir = "bin"
|
|
23
23
|
# spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
24
24
|
spec.require_paths = ["lib"]
|
|
25
|
-
spec.required_ruby_version = ">= 3.
|
|
25
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.3.0")
|
|
26
26
|
|
|
27
27
|
spec.add_runtime_dependency "asciidoctor"
|
|
28
28
|
spec.add_runtime_dependency "concurrent-ruby"
|