metanorma 2.1.4 → 2.5.4

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +23 -2
  3. data/Gemfile +14 -0
  4. data/README.adoc +1 -1
  5. data/lib/metanorma/collection/artifact_store.rb +142 -0
  6. data/lib/metanorma/collection/collection.rb +140 -131
  7. data/lib/metanorma/collection/config/bibdata.rb +1 -1
  8. data/lib/metanorma/collection/config/config.rb +50 -20
  9. data/lib/metanorma/collection/config/converters.rb +89 -31
  10. data/lib/metanorma/collection/config/doc_container.rb +28 -0
  11. data/lib/metanorma/collection/config/manifest.rb +30 -6
  12. data/lib/metanorma/collection/config/namespaces.rb +12 -0
  13. data/lib/metanorma/collection/document/document.rb +54 -15
  14. data/lib/metanorma/collection/filelookup/filelookup.rb +170 -74
  15. data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +62 -27
  16. data/lib/metanorma/collection/filelookup/utils.rb +52 -0
  17. data/lib/metanorma/collection/helpers.rb +82 -0
  18. data/lib/metanorma/collection/log.rb +24 -0
  19. data/lib/metanorma/collection/manifest/manifest.rb +27 -14
  20. data/lib/metanorma/collection/multilingual/multilingual.rb +1 -1
  21. data/lib/metanorma/collection/renderer/filelocation.rb +166 -0
  22. data/lib/metanorma/collection/renderer/fileparse.rb +60 -55
  23. data/lib/metanorma/collection/renderer/fileprocess.rb +184 -40
  24. data/lib/metanorma/collection/renderer/navigation.rb +20 -6
  25. data/lib/metanorma/collection/renderer/render_word.rb +8 -4
  26. data/lib/metanorma/collection/renderer/renderer.rb +202 -19
  27. data/lib/metanorma/collection/renderer/svg.rb +60 -5
  28. data/lib/metanorma/collection/renderer/utils.rb +76 -42
  29. data/lib/metanorma/collection/sectionsplit/collection.rb +14 -4
  30. data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +141 -61
  31. data/lib/metanorma/collection/util/disambig_files.rb +4 -5
  32. data/lib/metanorma/collection/util/util.rb +147 -23
  33. data/lib/metanorma/collection/xrefprocess/xrefprocess.rb +7 -7
  34. data/lib/metanorma/compile/assets/icc-boilerplate.adoc +41 -0
  35. data/lib/metanorma/compile/compile.rb +180 -165
  36. data/lib/metanorma/compile/compile_options.rb +155 -83
  37. data/lib/metanorma/compile/extract.rb +76 -56
  38. data/lib/metanorma/compile/flavor.rb +19 -0
  39. data/lib/metanorma/compile/output_filename.rb +75 -0
  40. data/lib/metanorma/compile/output_filename_config.rb +27 -0
  41. data/lib/metanorma/compile/relaton_drop.rb +56 -0
  42. data/lib/metanorma/compile/render.rb +196 -0
  43. data/lib/metanorma/compile/validator.rb +26 -0
  44. data/lib/metanorma/compile/writeable.rb +12 -0
  45. data/lib/metanorma/util/fontist_helper.rb +35 -19
  46. data/lib/metanorma/version.rb +1 -1
  47. data/lib/metanorma.rb +1 -7
  48. data/metanorma.gemspec +11 -20
  49. data/plans/collection-rendering-architecture-review.md +516 -0
  50. metadata +42 -122
  51. data/.hound.yml +0 -5
  52. data/lib/metanorma/asciidoctor_extensions/glob_include_processor.rb +0 -22
  53. data/lib/metanorma/asciidoctor_extensions.rb +0 -1
  54. data/lib/metanorma/compile/compile_validate.rb +0 -68
  55. data/lib/metanorma/config/config.rb +0 -23
  56. data/lib/metanorma/input/asciidoc.rb +0 -88
  57. data/lib/metanorma/input/base.rb +0 -9
  58. data/lib/metanorma/input.rb +0 -7
  59. data/lib/metanorma/processor/processor.rb +0 -54
  60. data/lib/metanorma/processor.rb +0 -6
  61. data/lib/metanorma/registry/registry.rb +0 -63
  62. data/lib/metanorma/util/util.rb +0 -45
@@ -0,0 +1,41 @@
1
+ == copyright-statement
2
+ === Copyright notice
3
+
4
+ Copyright © 1994-{{docyear}} International Color Consortium®
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ the Specification and associated documentation files (the “Specification”) to
8
+ deal in the Specification without restriction, including without limitation the
9
+ rights to use, copy, modify, merge, publish, distribute, and/or sublicense
10
+ copies of the Specification, and to permit persons to whom the Specification is
11
+ furnished to do so, subject to the following conditions.
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Specification.
15
+
16
+ The Specification is provided "as is", without warranty of any kind, express,
17
+ implied, or otherwise, including but not limited to the warranties of
18
+ merchantability, fitness for a particular purpose and noninfringement. In no
19
+ event shall the International Color Consortium be liable for any claim, damages
20
+ or other liability, whether in an action of contract, tort or otherwise, arising
21
+ from, out of, or in connection with the Specification or the use or other
22
+ dealings in the Specification.
23
+
24
+ Except as contained in this notice, the name of the International Color
25
+ Consortium shall not be used in advertising or otherwise to promote the use or
26
+ other dealings in this Specification without prior written authorization from
27
+ the International Color Consortium.
28
+
29
+ == license-statement
30
+ === Licenses and trademarks
31
+
32
+ International Color Consortium and the ICC logo are registered trademarks of the
33
+ International Color Consortium. Rather than put a trademark symbol in every
34
+ occurrence of other trademarked names, we state that we are using the names only
35
+ in an editorial fashion, and to the benefit of the trademark owner, with no
36
+ intention of infringement of the trademark.
37
+
38
+ == feedback-statement
39
+ === For additional information on the ICC
40
+
41
+ Visit the ICC Web site: http://www.color.org
@@ -1,21 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "fileutils"
2
4
  require "nokogiri"
3
5
  require "htmlentities"
4
6
  require "yaml"
5
7
  require "fontist"
6
- require "fontist/manifest/install"
7
- require_relative "compile_validate"
8
+ require "fontist/manifest"
9
+ require "metanorma-taste"
10
+ require_relative "writeable"
11
+ require_relative "validator"
8
12
  require_relative "compile_options"
9
13
  require_relative "../util/fontist_helper"
10
- require_relative "../util/util"
14
+ require "metanorma-core"
11
15
  require_relative "extract"
12
16
  require_relative "../collection/sectionsplit/sectionsplit"
13
17
  require_relative "../util/worker_pool"
18
+ require_relative "output_filename"
19
+ require_relative "output_filename_config"
20
+ require_relative "flavor"
21
+ require_relative "relaton_drop"
22
+ require_relative "render"
14
23
 
15
24
  module Metanorma
16
25
  class Compile
26
+ include Validator
27
+ include CompileOptions
28
+ include Flavor
29
+ include Writeable
30
+
31
+ DEFAULT_NUM_WORKERS = 3
32
+
17
33
  # @return [Array<String>]
18
- attr_reader :errors, :processor
34
+ attr_reader :errors
35
+ attr_reader :processor
19
36
 
20
37
  def initialize
21
38
  @registry = Metanorma::Registry.instance
@@ -25,22 +42,43 @@ module Metanorma
25
42
  @log = Metanorma::Utils::Log.new
26
43
  end
27
44
 
45
+ # Main compile method that orchestrates the document conversion process
46
+ # @param filename [String] path to the input file
47
+ # @param options [Hash] compilation options
28
48
  def compile(filename, options = {})
29
- options_process(filename, options)
49
+ process_options!(filename, options)
30
50
  @processor = @registry.find_processor(options[:type].to_sym)
31
- (file, isodoc = process_input(filename, options)) or return nil
32
- extensions = get_extensions(options) or return nil
33
- relaton_export(isodoc, options)
34
- extract(isodoc, options[:extract], options[:extract_type])
35
- process_exts(filename, extensions, file, isodoc, options)
51
+
52
+ # Step 1: Generate Semantic XML
53
+ semantic_result = generate_semantic_xml(filename, options) or return nil
54
+ source_file, semantic_xml = semantic_result
55
+
56
+ # Step 2: Prepare output paths
57
+ xml = Nokogiri::XML(semantic_xml, &:huge)
58
+ bibdata = extract_relaton_metadata(xml)
59
+ output_paths = prepare_output_paths(filename, bibdata, options)
60
+
61
+ # Step 3: Determine which output formats to generate
62
+ extensions = get_extensions(options)
63
+ @extensions = extensions # Capture extensions for later access
64
+ extensions or return nil
65
+
66
+ # Step 4: Extract information from Semantic XML if requested
67
+ extract_information(semantic_xml, bibdata, options)
68
+
69
+ # Step 5: Generate output formats from Semantic XML
70
+ generate_outputs(
71
+ source_file, semantic_xml, bibdata, extensions, output_paths,
72
+ options
73
+ )
36
74
  ensure
37
75
  clean_exit(options)
38
76
  end
39
77
 
40
- def options_process(filename, options)
78
+ def process_options!(filename, options)
41
79
  require_libraries(options)
42
- options = options_extract(filename, options)
43
- validate_options(options)
80
+ options = extract_options(filename, options)
81
+ validate_options!(options)
44
82
  @log.save_to(filename, options[:output_dir])
45
83
  options[:log] = @log
46
84
  end
@@ -50,7 +88,11 @@ module Metanorma
50
88
  @log.write
51
89
  end
52
90
 
53
- def process_input(filename, options)
91
+ # Step 1: Generate Semantic XML from input file
92
+ # @param filename [String] input file path
93
+ # @param options [Hash] compilation options
94
+ # @return [Array, nil] tuple of [source_file, semantic_xml] or nil on failure
95
+ def generate_semantic_xml(filename, options)
54
96
  case extname = File.extname(filename)
55
97
  when ".adoc" then process_input_adoc(filename, options)
56
98
  when ".xml" then process_input_xml(filename, options)
@@ -61,184 +103,157 @@ module Metanorma
61
103
  end
62
104
  end
63
105
 
64
- def process_input_adoc(filename, options)
65
- Util.log("[metanorma] Processing: AsciiDoc input.", :info)
66
- file = read_file(filename)
67
- options[:asciimath] and
68
- file.sub!(/^(=[^\n]+\n)/, "\\1:mn-keep-asciimath:\n")
69
- dir = File.dirname(filename)
70
- dir != "." and
71
- file = file.gsub(/^include::/, "include::#{dir}/")
72
- .gsub(/^embed::/, "embed::#{dir}/")
73
- [file, @processor.input_to_isodoc(file, filename, options)]
74
- end
75
-
76
- def process_input_xml(filename, _options)
77
- Util.log("[metanorma] Processing: Metanorma XML input.", :info)
78
- # TODO NN: this is a hack -- we should provide/bridge the
79
- # document attributes in Metanorma XML
80
- ["", read_file(filename)]
106
+ # Step 2: Prepare output paths for generated files
107
+ # Use default filename template if empty string is provided.
108
+ #
109
+ # @param filename [String] input file path
110
+ # @param bibdata [Nokogiri::XML::Element] the bibliographic data element
111
+ # @param options [Hash] compilation options
112
+ # @return [Hash] paths for different output formats
113
+ def prepare_output_paths(filename, bibdata, options)
114
+ basename = if options[:filename_template].nil?
115
+ filename.sub(/\.[^.]+$/, "")
116
+ else
117
+ drop = RelatonDrop.new(bibdata)
118
+ config = OutputFilenameConfig.new(options[:filename_template])
119
+ config.generate_filename(drop)
120
+ end
121
+ @output_filename = OutputFilename.new(
122
+ basename, options[:output_dir], @processor
123
+ )
124
+ {
125
+ xml: @output_filename.semantic_xml,
126
+ orig_filename: filename,
127
+ presentationxml: @output_filename.presentation_xml,
128
+ }
81
129
  end
82
130
 
83
- def read_file(filename)
84
- File.read(filename, encoding: "utf-8").gsub("\r\n", "\n")
131
+ # Step 4: Extract information from Semantic XML
132
+ # @param semantic_xml [String] semantic XML content
133
+ # @param options [Hash] compilation options
134
+ def extract_information(semantic_xml, bibdata, options)
135
+ # Extract Relaton bibliographic data
136
+ export_relaton_from_bibdata(bibdata, options) if options[:relaton]
137
+ # Extract other components (sourcecode, images, requirements)
138
+ if options[:extract]
139
+ Extract.extract(
140
+ semantic_xml, options[:extract], options[:extract_type]
141
+ )
142
+ end
85
143
  end
86
144
 
87
- def relaton_export(isodoc, options)
88
- return unless options[:relaton]
89
-
90
- xml = Nokogiri::XML(isodoc, &:huge)
91
- bibdata = xml.at("//bibdata") || xml.at("//xmlns:bibdata")
92
- # docid = bibdata&.at("./xmlns:docidentifier")&.text || options[:filename]
93
- # outname = docid.sub(/^\s+/, "").sub(/\s+$/, "").gsub(/\s+/, "-") + ".xml"
94
- File.open(options[:relaton], "w:UTF-8") { |f| f.write bibdata.to_xml }
145
+ # Step 5: Generate output formats from Semantic XML
146
+ # @param source_file [String] source file content
147
+ # @param semantic_xml [String] semantic XML content
148
+ # @param bibdata [Nokogiri::XML::Element] the bibliographic data element
149
+ # @param extensions [Array<Symbol>] output formats to generate
150
+ # @param output_paths [Hash] paths for output files
151
+ # @param options [Hash] compilation options
152
+ def generate_outputs(
153
+ source_file, semantic_xml, bibdata, extensions, output_paths, options
154
+ )
155
+ if extensions == %i(presentation)
156
+ # Just generate presentation XML
157
+ generate_presentation_xml(
158
+ source_file, semantic_xml, bibdata, output_paths, options
159
+ )
160
+ else
161
+ # Generate multiple output formats with parallel processing
162
+ generate_outputs_parallel(
163
+ source_file, semantic_xml, bibdata, extensions, output_paths, options
164
+ )
165
+ end
95
166
  end
96
167
 
97
- def export_output(fname, content, **options)
98
- mode = options[:binary] ? "wb" : "w:UTF-8"
99
- File.open(fname, mode) { |f| f.write content }
168
+ def process_input_adoc_hdr(file, options)
169
+ hdr, rest = Metanorma::Input::Asciidoc.new.header(file)
170
+ attrs = hdr.split("\n")
171
+ options[:asciimath] and attrs << ":mn-keep-asciimath:"
172
+ process_input_adoc_overrides(attrs, options)
173
+ "#{attrs.join("\n")}\n\n#{rest}"
100
174
  end
101
175
 
102
- def wrap_html(options, file_extension, outfilename)
103
- if options[:wrapper] && /html$/.match(file_extension)
104
- outfilename = outfilename.sub(/\.html$/, "")
105
- FileUtils.mkdir_p outfilename
106
- FileUtils.mv "#{outfilename}.html", outfilename
107
- FileUtils.mv "#{outfilename}_images", outfilename, force: true
108
- end
176
+ def process_input_adoc_overrides(attrs, options)
177
+ Metanorma::TasteRegister.instance.available_tastes
178
+ .include?(options[:supplied_type]) or return
179
+ c = Metanorma::TasteRegister.get(options[:supplied_type])
180
+ c.process_input_adoc_overrides(attrs, options)
109
181
  end
110
182
 
111
- # isodoc is Raw Metanorma XML
112
- def process_exts(filename, extensions, file, isodoc, options)
113
- f = File.expand_path(change_output_dir(options))
114
- fnames = { xml: f.sub(/\.[^.]+$/, ".xml"), f: f,
115
- orig_filename: File.expand_path(filename),
116
- presentationxml: f.sub(/\.[^.]+$/, ".presentation.xml") }
117
- if extensions == %i(presentation)
118
- process_ext(:presentation, file, isodoc, fnames, options)
119
- else
120
- process_exts_queue(fnames, extensions, file, isodoc, options)
121
- end
183
+ def process_input_adoc_includes(file, filename)
184
+ dir = File.dirname(filename)
185
+ dir != "." and
186
+ file = file.gsub(/^include::/, "include::#{dir}/")
187
+ .gsub(/^embed::/, "embed::#{dir}/")
188
+ file
122
189
  end
123
190
 
124
- def process_exts_queue(fnames, extensions, file, isodoc, options)
125
- @queue = ::Metanorma::Util::WorkersPool
126
- .new(ENV["METANORMA_PARALLEL"]&.to_i || 3)
127
- gather_and_install_fonts(file, options.dup, extensions)
128
- process_exts_run(fnames, file, isodoc, extensions, options)
129
- @queue.shutdown
191
+ def process_input_adoc(filename, options)
192
+ Util.log("[metanorma] Processing: AsciiDoc input.", :info)
193
+ file = read_file(filename)
194
+ file = process_input_adoc_hdr(file, options)
195
+ file = process_input_adoc_includes(file, filename)
196
+ update_options_after_preprocessing(file, options)
197
+ [file, @processor.input_to_isodoc(file, filename, options)]
130
198
  end
131
199
 
132
- def process_exts_run(fnames, file, isodoc, extensions, options)
133
- Util.sort_extensions_execution(extensions).each do |ext|
134
- process_ext(ext, file, isodoc, fnames, options) or break
200
+ # need to update options, for any content added;
201
+ # e.g. new attributes from Metanorma taste
202
+ def update_options_after_preprocessing(file, options)
203
+ new_options = { extension_keys: options[:extension_keys] }
204
+ Tempfile.open(["tmp", ".adoc"], encoding: "UTF-8") do |f|
205
+ f.write(file)
206
+ f.close
207
+ new_options = extract_options(f, new_options)
135
208
  end
209
+ new_options.compact!
210
+ new_options.delete(:filename)
211
+ options.merge!(new_options)
136
212
  end
137
213
 
138
- def gather_and_install_fonts(file, options, extensions)
139
- Util.sort_extensions_execution(extensions).each do |ext|
140
- isodoc_options = get_isodoc_options(file, options, ext)
141
- font_install(isodoc_options.merge(options))
142
- end
214
+ def process_input_xml(filename, _options)
215
+ Util.log("[metanorma] Processing: Metanorma XML input.", :info)
216
+ # TODO NN: this is a hack -- we should provide/bridge the
217
+ # document attributes in Metanorma XML
218
+ ["", read_file(filename)]
143
219
  end
144
220
 
145
- def process_ext(ext, file, isodoc, fnames, options)
146
- fnames[:ext] = @processor.output_formats[ext]
147
- fnames[:out] = fnames[:f].sub(/\.[^.]+$/, ".#{fnames[:ext]}")
148
- isodoc_options = get_isodoc_options(file, options, ext)
149
- thread = true
150
- unless process_ext_simple(ext, isodoc, fnames, options,
151
- isodoc_options)
152
- thread = process_exts1(ext, fnames, isodoc, options, isodoc_options)
153
- end
154
- thread
155
- end
156
-
157
- def process_ext_simple(ext, isodoc, fnames, options, isodoc_options)
158
- if ext == :rxl
159
- relaton_export(isodoc, options.merge(relaton: fnames[:out]))
160
- elsif options[:passthrough_presentation_xml] && ext == :presentation
161
- #f = File.exist?(fnames[:f]) ? fnames[:f] : fnames[:orig_filename]
162
- f = File.exist?(fnames[:orig_filename]) ? fnames[:orig_filename] : fnames[:f]
163
- FileUtils.cp f, fnames[:presentationxml]
164
- elsif ext == :html && options[:sectionsplit]
165
- sectionsplit_convert(fnames[:xml], isodoc, fnames[:out],
166
- isodoc_options)
167
- else return false
168
- end
169
- true
221
+ def read_file(filename)
222
+ File.read(filename, encoding: "utf-8").gsub("\r\n", "\n")
170
223
  end
171
224
 
172
- def process_exts1(ext, fnames, isodoc, options, isodoc_options)
173
- if @processor.use_presentation_xml(ext)
174
- @queue.schedule(ext, fnames.dup, options.dup,
175
- isodoc_options.dup) do |a, b, c, d|
176
- process_output_threaded(a, b, c, d)
225
+ # Returns a mapping of format types to file suffixes based on the
226
+ # extensions that were determined during compilation.
227
+ # This method is only available after compile() has been called and
228
+ # reached Step 3 where extensions are finalized.
229
+ #
230
+ # @return [Hash] mapping of format symbols to file extensions
231
+ # e.g., { html: "html", doc: "doc", pdf: "pdf" }
232
+ # @return [Hash] empty hash if extensions haven't been captured yet
233
+ def output_format_mapping
234
+ @extensions && @processor or return {}
235
+ @extensions.each_with_object({}) do |ext, mapping|
236
+ if @processor.output_formats[ext]
237
+ mapping[ext] = @processor.output_formats[ext]
177
238
  end
178
- else
179
- process_output_unthreaded(ext, fnames, isodoc, isodoc_options)
180
239
  end
181
240
  end
182
241
 
183
- def process_output_threaded(ext, fnames1, options1, isodoc_options1)
184
- @processor.output(nil, fnames1[:presentationxml], fnames1[:out], ext,
185
- isodoc_options1)
186
- wrap_html(options1, fnames1[:ext], fnames1[:out])
187
- rescue StandardError => e
188
- strict = ext == :presentation || isodoc_options1[:strict] == true
189
- isodoc_error_process(e, strict, false)
190
- end
191
-
192
- def process_output_unthreaded(ext, fnames, isodoc, isodoc_options)
193
- @processor.output(isodoc, fnames[:xml], fnames[:out], ext,
194
- isodoc_options)
195
- true # return as Thread
196
- rescue StandardError => e
197
- strict = ext == :presentation || isodoc_options[:strict] == "true"
198
- isodoc_error_process(e, strict, true)
199
- ext != :presentation
200
- end
201
-
202
- # assume we pass in Presentation XML, but we want to recover Semantic XML
203
- def sectionsplit_convert(input_filename, file, output_filename = nil,
204
- opts = {})
205
- @isodoc ||= IsoDoc::PresentationXMLConvert.new({})
206
- input_filename += ".xml" unless input_filename.match?(/\.xml$/)
207
- File.exist?(input_filename) or
208
- File.open(input_filename, "w:UTF-8") { |f| f.write(file) }
209
- presxml = File.read(input_filename, encoding: "utf-8")
210
- _xml, filename, dir = @isodoc.convert_init(presxml, input_filename, false)
211
-
212
- ::Metanorma::Collection::Sectionsplit.new(
213
- input: input_filename,
214
- isodoc: @isodoc,
215
- xml: presxml,
216
- base: File.basename(output_filename || filename),
217
- output: output_filename || filename,
218
- dir: dir,
219
- compile_opts: opts,
220
- ).build_collection
221
- end
222
-
223
- private
224
-
225
- def isodoc_error_process(err, strict, must_abort)
226
- if strict || err.message.include?("Fatal:")
227
- @errors << err.message
228
- else
229
- puts err.message
230
- end
231
- puts err.backtrace.join("\n")
232
- must_abort and 1
242
+ def process_compile_dummy(flavor)
243
+ file = Tempfile.new(%w(tmp .adoc))
244
+ file.write <<~OUTPUT
245
+ Document Title
246
+ :flavor: #{flavor}
247
+ :mn-output-extensions: xml, presentation
248
+
249
+ OUTPUT
250
+ file.close
251
+ compile(file.path, agree_to_terms: true)
233
252
  end
234
253
 
235
- # @param options [Hash]
236
- # @return [String]
237
- def change_output_dir(options)
238
- if options[:output_dir]
239
- File.join options[:output_dir], File.basename(options[:filename])
240
- else options[:filename]
241
- end
254
+ def extract_log_messages(flavor)
255
+ process_compile_dummy(flavor)
256
+ @log.display_messages
242
257
  end
243
258
  end
244
259
  end