metanorma 2.0.0 → 2.5.5

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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +23 -2
  5. data/Gemfile +17 -0
  6. data/Gemfile.devel +10 -0
  7. data/README.adoc +1 -1
  8. data/lib/metanorma/collection/artifact_store.rb +142 -0
  9. data/lib/metanorma/collection/collection.rb +160 -122
  10. data/lib/metanorma/collection/config/bibdata.rb +3 -3
  11. data/lib/metanorma/collection/config/compile_options.rb +4 -5
  12. data/lib/metanorma/collection/config/config.rb +81 -83
  13. data/lib/metanorma/collection/config/converters.rb +92 -8
  14. data/lib/metanorma/collection/config/directive.rb +9 -3
  15. data/lib/metanorma/collection/config/doc_container.rb +28 -0
  16. data/lib/metanorma/collection/config/manifest.rb +59 -29
  17. data/lib/metanorma/collection/config/namespaces.rb +12 -0
  18. data/lib/metanorma/collection/document/document.rb +56 -15
  19. data/lib/metanorma/collection/filelookup/filelookup.rb +185 -77
  20. data/lib/metanorma/collection/filelookup/filelookup_sectionsplit.rb +99 -27
  21. data/lib/metanorma/collection/filelookup/utils.rb +52 -0
  22. data/lib/metanorma/collection/helpers.rb +82 -0
  23. data/lib/metanorma/collection/log.rb +24 -0
  24. data/lib/metanorma/collection/manifest/manifest.rb +45 -14
  25. data/lib/metanorma/collection/multilingual/multilingual.rb +660 -0
  26. data/lib/metanorma/collection/renderer/filelocation.rb +166 -0
  27. data/lib/metanorma/collection/renderer/fileparse.rb +135 -115
  28. data/lib/metanorma/collection/renderer/fileprocess.rb +185 -37
  29. data/lib/metanorma/collection/renderer/navigation.rb +21 -7
  30. data/lib/metanorma/collection/renderer/render_word.rb +15 -10
  31. data/lib/metanorma/collection/renderer/renderer.rb +236 -32
  32. data/lib/metanorma/collection/renderer/svg.rb +107 -0
  33. data/lib/metanorma/collection/renderer/utils.rb +110 -39
  34. data/lib/metanorma/collection/sectionsplit/collection.rb +110 -0
  35. data/lib/metanorma/collection/sectionsplit/sectionsplit.rb +198 -115
  36. data/lib/metanorma/collection/util/disambig_files.rb +4 -5
  37. data/lib/metanorma/collection/util/util.rb +155 -18
  38. data/lib/metanorma/collection/xrefprocess/xrefprocess.rb +47 -33
  39. data/lib/metanorma/compile/assets/icc-boilerplate.adoc +41 -0
  40. data/lib/metanorma/compile/compile.rb +181 -156
  41. data/lib/metanorma/compile/compile_options.rb +186 -79
  42. data/lib/metanorma/compile/extract.rb +75 -58
  43. data/lib/metanorma/compile/flavor.rb +28 -0
  44. data/lib/metanorma/compile/output_filename.rb +75 -0
  45. data/lib/metanorma/compile/output_filename_config.rb +27 -0
  46. data/lib/metanorma/compile/relaton_drop.rb +56 -0
  47. data/lib/metanorma/compile/render.rb +226 -0
  48. data/lib/metanorma/compile/validator.rb +26 -0
  49. data/lib/metanorma/compile/writeable.rb +12 -0
  50. data/lib/metanorma/util/fontist_helper.rb +35 -19
  51. data/lib/metanorma/version.rb +1 -1
  52. data/lib/metanorma.rb +1 -7
  53. data/metanorma.gemspec +15 -21
  54. data/plans/collection-rendering-architecture-review.md +516 -0
  55. metadata +65 -113
  56. data/.hound.yml +0 -5
  57. data/lib/metanorma/asciidoctor_extensions/glob_include_processor.rb +0 -22
  58. data/lib/metanorma/asciidoctor_extensions.rb +0 -1
  59. data/lib/metanorma/compile/compile_validate.rb +0 -68
  60. data/lib/metanorma/config/config.rb +0 -23
  61. data/lib/metanorma/input/asciidoc.rb +0 -107
  62. data/lib/metanorma/input/base.rb +0 -9
  63. data/lib/metanorma/input.rb +0 -7
  64. data/lib/metanorma/processor/processor.rb +0 -54
  65. data/lib/metanorma/processor.rb +0 -6
  66. data/lib/metanorma/registry/registry.rb +0 -63
  67. data/lib/metanorma/shale_monkeypatch.rb +0 -15
  68. data/lib/metanorma/util/util.rb +0 -45
@@ -2,101 +2,208 @@ require "csv"
2
2
 
3
3
  module Metanorma
4
4
  class Compile
5
- def require_libraries(options)
6
- options&.dig(:require)&.each { |r| require r }
7
- end
5
+ module CompileOptions
6
+ def require_libraries(options)
7
+ options&.dig(:require)&.each { |r| require r }
8
+ end
8
9
 
9
- def xml_options_extract(file)
10
- xml = Nokogiri::XML(file, &:huge)
11
- if xml.root
12
- @registry.root_tags.each do |k, v|
13
- return { type: k } if v == xml.root.name
10
+ def extract_xml_options(file)
11
+ xml = Nokogiri::XML(file, &:huge)
12
+ if xml.root
13
+ @registry.root_tags.each do |k, v|
14
+ return { type: k } if v == xml.root.name
15
+ end
14
16
  end
17
+ {}
15
18
  end
16
- {}
17
- end
18
19
 
19
- def options_extract(filename, options)
20
- content = read_file(filename)
21
- o = Metanorma::Input::Asciidoc.new.extract_metanorma_options(content)
22
- .merge(xml_options_extract(content))
23
- options[:type] ||= o[:type]&.to_sym
24
- t = @registry.alias(options[:type]) and options[:type] = t
25
- dir = filename.sub(%r(/[^/]+$), "/")
26
- options[:relaton] ||= File.join(dir, o[:relaton]) if o[:relaton]
27
- options[:sourcecode] ||= File.join(dir, o[:sourcecode]) if o[:sourcecode]
28
- options[:extension_keys] ||= o[:extensions]&.split(/, */)&.map(&:to_sym)
29
- options[:extension_keys] = nil if options[:extension_keys] == [:all]
30
- options[:format] ||= :asciidoc
31
- options[:filename] = filename
32
- options[:fontlicenseagreement] ||= "no-install-fonts"
33
- options[:novalid] = o[:novalid] if o[:novalid]
34
- options
35
- end
20
+ def extract_options(filename, options)
21
+ o = options_in_file(filename)
22
+ extract_flavor_options(options, o)
23
+ extract_dir_options(options, o, filename)
24
+ extract_extension_options(options, o)
25
+ options[:format] ||= :asciidoc
26
+ options[:filename] = filename
27
+ options[:fontlicenseagreement] ||= "no-install-fonts"
28
+ options[:novalid] = o[:novalid] if o[:novalid]
29
+ options
30
+ end
36
31
 
37
- def get_extensions(options)
38
- ext = extract_extensions(options)
39
- !ext.include?(:presentation) && ext.any? do |e|
40
- @processor.use_presentation_xml(e)
41
- end and ext << :presentation
42
- !ext.include?(:rxl) && options[:site_generate] and
43
- ext << :rxl
44
- ext
45
- end
32
+ def extract_extension_options(opt, doc_opt)
33
+ opt[:extension_keys] == [:all] and opt[:extension_keys] = []
34
+ ext = opt[:extension_keys] || []
35
+ doc_opt[:extensions] == "all" and doc_opt[:extensions] = ""
36
+ ext1 = (doc_opt[:extensions] || "").split(/, */).map(&:to_sym)
37
+ !ext1.empty? && ext.empty? and opt[:extension_keys] = ext1
38
+ opt[:extension_keys]&.empty? and opt[:extension_keys] = nil
39
+ end
40
+
41
+ def options_in_file(filename)
42
+ # the adoc preprocessing path re-extracts options from a Tempfile
43
+ filename = filename.path if filename.is_a?(Tempfile)
44
+ content = read_file(filename)
45
+ # XML inputs carry no AsciiDoc header options; booting the adoc
46
+ # pipeline for them only drags every plugin into the compile.
47
+ return extract_xml_options(content) if filename.end_with?(".xml")
48
+
49
+ Metanorma::Input::Asciidoc.new.extract_metanorma_options(content)
50
+ .merge(extract_xml_options(content))
51
+ end
52
+
53
+ def extract_flavor_options(options, options_in_file)
54
+ options[:type] ||= options_in_file[:type]
55
+ options[:type] = options[:type]&.to_sym
56
+ options[:supplied_type] = options[:type]
57
+ t = @registry.alias(options[:type]) and options[:type] = t
58
+ end
59
+
60
+ def extract_dir_options(options, options_in_file, filename)
61
+ filename.is_a?(Tempfile) and return
62
+ dir = filename.sub(%r(/[^/]+$), "/")
63
+ options_in_file[:relaton] and
64
+ options[:relaton] ||= File.join(dir,
65
+ options_in_file[:relaton])
66
+ options_in_file[:sourcecode] and
67
+ options[:sourcecode] ||= File.join(dir,
68
+ options_in_file[:sourcecode])
69
+ end
70
+
71
+ # Document-model transformer specs contributed by the active taste
72
+ # (+options[:supplied_type]+), or +{}+. Lets a taste (e.g. OIML) make its
73
+ # own output format selectable and choose its presentation/semantic leg,
74
+ # via the taste-aware metanorma-core Processor (metanorma-core#12).
75
+ def taste_transformers(options)
76
+ taste = options[:supplied_type]
77
+ return {} unless taste && defined?(Metanorma::TasteRegister) &&
78
+ Metanorma::TasteRegister.respond_to?(:document_transformers_for)
79
+
80
+ Metanorma::TasteRegister.document_transformers_for(taste) || {}
81
+ end
82
+
83
+ # Harness formats available to every flavor, regardless of the
84
+ # processor's own list. MKO (metanorma-document) is resolved via
85
+ # Core::Flavors, so it needs nothing from the flavor gems.
86
+ HARNESS_OUTPUT_FORMATS = { mko: "mko" }.freeze
87
+
88
+ # Formats a flavor offers by default: its own list plus
89
+ # taste-contributed document-model formats. The harness formats
90
+ # (mko) are deliberately absent — they are opt-in via
91
+ # --extensions, never a surprise default output.
92
+ def default_output_formats(options)
93
+ @processor.output_formats
94
+ .merge(
95
+ taste_transformers(options)
96
+ .transform_values { |spec| spec[:suffix] }.compact,
97
+ )
98
+ end
46
99
 
47
- def extract_extensions(options)
48
- options[:extension_keys] ||=
49
- @processor.output_formats.reduce([]) { |memo, (k, _)| memo << k }
50
- options[:extension_keys].reduce([]) do |memo, e|
51
- if @processor.output_formats[e] then memo << e
52
- else
53
- unsupported_format_error(e)
54
- memo
100
+ # The default formats merged with the harness formats, so those
101
+ # pass extension validation and get an output suffix.
102
+ def effective_output_formats(options)
103
+ default_output_formats(options).merge(HARNESS_OUTPUT_FORMATS)
104
+ end
105
+
106
+ # Whether +ext+ is generated from presentation XML, honouring both the
107
+ # processor's own answer and a taste transformer's +:presentation+ flag.
108
+ def uses_presentation_xml?(ext, options)
109
+ ext == :mko || # unit numbering comes from the presentation model
110
+ @processor.use_presentation_xml(ext) ||
111
+ (taste_transformers(options)[ext] || {})[:presentation] == true
112
+ end
113
+
114
+ def get_extensions(options)
115
+ ext = extract_extensions(options)
116
+ !ext.include?(:presentation) && ext.any? do |e|
117
+ uses_presentation_xml?(e, options)
118
+ end and ext << :presentation
119
+ !ext.include?(:rxl) && options[:site_generate] and
120
+ ext << :rxl
121
+ # Keep options[:extension_keys] in sync: get_isodoc_options filters
122
+ # output_formats by extension_keys, and presentation is an implicit
123
+ # dependency of html/doc/pdf — if it is only on the local +ext+ array,
124
+ # the filter strips it from the formats table (and, when the processor
125
+ # hands out a live hash, permanently from the flavor config).
126
+ options[:extension_keys] = ext
127
+ ext
128
+ end
129
+
130
+ def extract_extensions(options)
131
+ formats = effective_output_formats(options)
132
+ options[:extension_keys] ||=
133
+ default_output_formats(options).reduce([]) { |memo, (k, _)| memo << k }
134
+ options[:extension_keys].reduce([]) do |memo, e|
135
+ if formats[e] then memo << e
136
+ else
137
+ unsupported_format_error(e)
138
+ memo
139
+ end
55
140
  end
56
141
  end
57
- end
58
142
 
59
- def font_install(opt)
60
- @fontist_installed or
61
- Util::FontistHelper.install_fonts(@processor, opt)
62
- @fontist_installed = true
63
- end
143
+ def font_install(opt)
144
+ @fontist_installed or
145
+ Util::FontistHelper.install_fonts(@processor, opt)
146
+ @fontist_installed = true
147
+ end
64
148
 
65
- private
149
+ private
66
150
 
67
- def unsupported_format_error(ext)
68
- message = "[metanorma] Error: #{ext} format is not supported " \
69
- "for this standard."
70
- @errors << message
71
- Util.log(message, :error)
72
- end
151
+ def unsupported_format_error(ext)
152
+ message = "[metanorma] Error: #{ext} format is not supported " \
153
+ "for this standard."
154
+ @errors << message
155
+ Util.log(message, :error)
156
+ end
73
157
 
74
- def get_isodoc_options(file, options, ext)
75
- ret = @processor.extract_options(file)
76
- copy_isodoc_options_attrs(options, ret)
77
- font_manifest_mn2pdf(options, ret, ext)
78
- ret[:output_formats]&.select! do |k, _|
79
- options[:extension_keys].include?(k)
158
+ def get_isodoc_options(file, options, ext)
159
+ ret = @processor.extract_options(file)
160
+ get_isodoc_fileparams(options, ret)
161
+ copy_isodoc_options_attrs(options, ret)
162
+ font_manifest_mn2pdf(options, ret, ext)
163
+ # Filter a COPY — never select! the processor's live formats table.
164
+ # Generic (and any flavor that returns configuration.formats directly)
165
+ # would otherwise permanently lose :presentation after the first
166
+ # html/doc request that did not list presentation in extension_keys.
167
+ if ret[:output_formats]
168
+ keys = options[:extension_keys] || []
169
+ ret[:output_formats] = ret[:output_formats]
170
+ .select { |k, _| keys.include?(k) }
171
+ end
172
+ ret[:log] = @log
173
+ ret
80
174
  end
81
- ret
82
- end
83
175
 
84
- def copy_isodoc_options_attrs(options, ret)
85
- ret[:datauriimage] = true if options[:datauriimage]
86
- ret[:sourcefilename] = options[:filename]
87
- %i(bare sectionsplit install_fonts baseassetpath aligncrosselements
88
- tocfigures toctables tocrecommendations strict)
89
- .each { |x| ret[x] ||= options[x] }
90
- end
176
+ def get_isodoc_fileparams(options, ret)
177
+ %i(i18nyaml relatonrenderconfig).each do |k|
178
+ dir = File.dirname(options[:filename])
179
+ ret[k] or next
180
+ (Pathname.new ret[k]).absolute? or
181
+ ret[k] = File.join(dir, ret[k])
182
+ end
183
+ end
184
+
185
+ def copy_isodoc_options_attrs(options, ret)
186
+ ret[:datauriimage] = true if options[:datauriimage]
187
+ ret[:sourcefilename] = options[:filename]
188
+ # Carry the active taste through to Processor#output, so its taste-aware
189
+ # document_transformers resolve there (metanorma-core#12).
190
+ ret[:supplied_type] = options[:supplied_type]
191
+ %i(bare sectionsplit sectionsplit_filename install_fonts baseassetpath
192
+ aligncrosselements tocfigures toctables tocrecommendations tocexamples
193
+ strict fonts)
194
+ .each { |x| ret[x] ||= options[x] }
195
+ end
91
196
 
92
- def font_manifest_mn2pdf(options, ret, ext)
93
- custom_fonts = Util::FontistHelper
94
- .has_custom_fonts?(@processor, options, ret)
197
+ def font_manifest_mn2pdf(options, ret, ext)
198
+ custom_fonts = Util::FontistHelper
199
+ .has_custom_fonts?(@processor, options, ret)
95
200
 
96
- ext == :pdf && custom_fonts and
97
- ret[:mn2pdf] = {
98
- font_manifest: Util::FontistHelper.location_manifest(@processor, ret),
99
- }
201
+ ext == :pdf && custom_fonts and
202
+ ret[:mn2pdf] = {
203
+ font_manifest: Util::FontistHelper
204
+ .location_manifest(@processor, ret),
205
+ }
206
+ end
100
207
  end
101
208
  end
102
209
  end
@@ -1,71 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "writeable"
4
+
1
5
  module Metanorma
2
6
  class Compile
3
- def relaton_export(isodoc, options)
4
- return unless options[:relaton]
7
+ module Extract
8
+ # @param isodoc [String] the XML document
9
+ # @param dirname [String, nil] the directory to extract to
10
+ # @param extract_types [Array<Symbol>, nil] the types to extract
11
+ # @return [void]
12
+ def self.extract(isodoc, dirname, extract_types)
13
+ dirname or return
14
+ extract_types.nil? || extract_types.empty? and
15
+ extract_types = %i[sourcecode image requirement]
16
+ FileUtils.rm_rf dirname
17
+ FileUtils.mkdir_p dirname
18
+ xml = Nokogiri::XML(isodoc, &:huge)
19
+ extract_types.each do |type|
20
+ case type
21
+ when :sourcecode
22
+ export_sourcecode(xml, dirname)
23
+ when :image
24
+ export_image(xml, dirname)
25
+ when :requirement
26
+ export_requirement(xml, dirname)
27
+ end
28
+ end
29
+ end
5
30
 
6
- xml = Nokogiri::XML(isodoc, &:huge)
7
- bibdata = xml.at("//bibdata") || xml.at("//xmlns:bibdata")
8
- # docid = bibdata&.at("./xmlns:docidentifier")&.text || options[:filename]
9
- # outname = docid.sub(/^\s+/, "").sub(/\s+$/, "").gsub(/\s+/, "-") + ".xml"
10
- File.open(options[:relaton], "w:UTF-8") { |f| f.write bibdata.to_xml }
11
- end
31
+ class << self
32
+ include Writeable
12
33
 
13
- def clean_sourcecode(xml)
14
- xml.xpath(".//callout | .//annotation | .//xmlns:callout | "\
15
- ".//xmlns:annotation").each(&:remove)
16
- xml.xpath(".//br | .//xmlns:br").each { |x| x.replace("\n") }
17
- HTMLEntities.new.decode(xml.children.to_xml)
18
- end
34
+ private
19
35
 
20
- def extract(isodoc, dirname, extract_types)
21
- return unless dirname
36
+ # @param xml [Nokogiri::XML::Document] the XML document
37
+ # @return [String] the cleaned sourcecode
38
+ def clean_sourcecode(xml)
39
+ xml.xpath(".//callout | .//callout-annotation | .//xmlns:callout | "\
40
+ ".//xmlns:callout-annotation").each(&:remove)
41
+ xml.xpath(".//br | .//xmlns:br").each { |x| x.replace("\n") }
42
+ a = xml.at("./body | ./xmlns:body") and xml = a
43
+ HTMLEntities.new.decode(xml.children.to_xml)
44
+ end
22
45
 
23
- extract_types.nil? || extract_types.empty? and
24
- extract_types = %i[sourcecode image requirement]
25
- FileUtils.rm_rf dirname
26
- FileUtils.mkdir_p dirname
27
- xml = Nokogiri::XML(isodoc, &:huge)
28
- sourcecode_export(xml, dirname) if extract_types.include? :sourcecode
29
- image_export(xml, dirname) if extract_types.include? :image
30
- extract_types.include?(:requirement) and
31
- requirement_export(xml, dirname)
32
- end
46
+ def export_sourcecode(xml, dirname)
47
+ xml.at("//sourcecode | //xmlns:sourcecode") or return
48
+ FileUtils.mkdir_p "#{dirname}/sourcecode"
49
+ xml.xpath("//sourcecode | //xmlns:sourcecode").each_with_index do |s, i|
50
+ filename = s["filename"] || sprintf("sourcecode-%04d.txt", i)
51
+ export_output("#{dirname}/sourcecode/#{filename}",
52
+ clean_sourcecode(s.dup))
53
+ end
54
+ end
33
55
 
34
- def sourcecode_export(xml, dirname)
35
- xml.at("//sourcecode | //xmlns:sourcecode") or return
36
- FileUtils.mkdir_p "#{dirname}/sourcecode"
37
- xml.xpath("//sourcecode | //xmlns:sourcecode").each_with_index do |s, i|
38
- filename = s["filename"] || sprintf("sourcecode-%04d.txt", i)
39
- export_output("#{dirname}/sourcecode/#{filename}",
40
- clean_sourcecode(s.dup))
41
- end
42
- end
43
-
44
- def image_export(xml, dirname)
45
- xml.at("//image | //xmlns:image") or return
46
- FileUtils.mkdir_p "#{dirname}/image"
47
- xml.xpath("//image | //xmlns:image").each_with_index do |s, i|
48
- next unless /^data:image/.match? s["src"]
56
+ def export_image(xml, dirname)
57
+ xml.at("//image | //xmlns:image") or return
58
+ FileUtils.mkdir_p "#{dirname}/image"
59
+ xml.xpath("//image | //xmlns:image").each_with_index do |s, i|
60
+ next unless /^data:image/.match? s["src"]
49
61
 
50
- %r{^data:image/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ s["src"]
51
- fn = s["filename"] || sprintf("image-%<num>04d.%<name>s",
52
- num: i, name: imgtype)
53
- export_output("#{dirname}/image/#{fn}", Base64.strict_decode64(imgdata),
54
- binary: true)
55
- end
56
- end
62
+ %r{^data:image/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ s["src"]
63
+ fn = s["filename"] || sprintf("image-%<num>04d.%<name>s",
64
+ num: i, name: imgtype)
65
+ export_output(
66
+ "#{dirname}/image/#{fn}",
67
+ Base64.strict_decode64(imgdata),
68
+ binary: true,
69
+ )
70
+ end
71
+ end
57
72
 
58
- REQUIREMENT_XPATH =
59
- "//requirement | //xmlns:requirement | //recommendation | "\
60
- "//xmlns:recommendation | //permission | //xmlns:permission".freeze
73
+ REQUIREMENT_XPATH =
74
+ "//requirement | //xmlns:requirement | //recommendation | "\
75
+ "//xmlns:recommendation | //permission | //xmlns:permission"
61
76
 
62
- def requirement_export(xml, dirname)
63
- xml.at(REQUIREMENT_XPATH) or return
64
- FileUtils.mkdir_p "#{dirname}/requirement"
65
- xml.xpath(REQUIREMENT_XPATH).each_with_index do |s, i|
66
- fn = s["filename"] ||
67
- sprintf("%<name>s-%<num>04d.xml", name: s.name, num: i)
68
- export_output("#{dirname}/requirement/#{fn}", s)
77
+ def export_requirement(xml, dirname)
78
+ xml.at(REQUIREMENT_XPATH) or return
79
+ FileUtils.mkdir_p "#{dirname}/requirement"
80
+ xml.xpath(REQUIREMENT_XPATH).each_with_index do |s, i|
81
+ fn = s["filename"] ||
82
+ sprintf("%<name>s-%<num>04d.xml", name: s.name, num: i)
83
+ export_output("#{dirname}/requirement/#{fn}", s)
84
+ end
85
+ end
69
86
  end
70
87
  end
71
88
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ class Compile
5
+ # Thin adapter: taste resolution reads the core table; gem loading
6
+ # stays on FlavorLoader so the existing SystemExit / processor-
7
+ # registration contract is byte-identical to main.
8
+ module Flavor
9
+ def load_flavor(stdtype)
10
+ Metanorma::Core::FlavorLoader.load_flavor(taste2flavor(stdtype))
11
+ end
12
+
13
+ def taste2flavor(stdtype)
14
+ entry = Metanorma::Core::Flavors.find(stdtype)
15
+ return entry.base_flavor if entry&.taste?
16
+
17
+ Metanorma::Core::FlavorLoader.taste2flavor(stdtype)
18
+ end
19
+
20
+ def stdtype2flavor_gem(stdtype)
21
+ entry = Metanorma::Core::Flavors.find(stdtype)
22
+ return entry.gem if entry && !entry.taste?
23
+
24
+ Metanorma::Core::FlavorLoader.stdtype2flavor_gem(stdtype)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ class Compile
5
+ class OutputFilename
6
+ # Returns an instance of OutputFilename from the source filename
7
+ # @param source_filename [String] the source filename
8
+ # @param output_dir [String, nil] the output directory
9
+ # @param processor [Metanorma::Processor, nil] the processor
10
+ # @return [OutputFilename] the instance of OutputFilename
11
+ def self.from_filename(source_filename, output_dir = nil, processor = nil)
12
+ new(strip_ext(source_filename), output_dir, processor)
13
+ end
14
+
15
+ class << self
16
+ private
17
+
18
+ def strip_ext(filename)
19
+ filename.sub(/\.[^.]*$/, "")
20
+ end
21
+ end
22
+
23
+ # @param noext_filename [String] the path (absolute/relative) of the source file, without extension (e.g., "/a/b/c/test")
24
+ # @param output_dir [String, nil] the output directory
25
+ # @param processor [Metanorma::Processor, nil] the processor
26
+ # @return [OutputFilename] the instance of OutputFilename
27
+ def initialize(noext_filename, output_dir = nil, processor = nil)
28
+ @noext_filename = noext_filename
29
+ @output_dir = output_dir
30
+ @processor = processor
31
+ end
32
+
33
+ # Returns the full file path name with the semantic XML extension
34
+ # @return [String] the full file path name with the semantic XML extension
35
+ def semantic_xml
36
+ with_extension("xml")
37
+ end
38
+
39
+ # Returns the full file path name with the presentation XML extension
40
+ # @return [String] the full file path name with the presentation XML extension
41
+ def presentation_xml
42
+ with_extension("presentation.xml")
43
+ end
44
+
45
+ # Returns the full file path name with the given format extension
46
+ # @param format [Symbol] the format
47
+ # @return [String, nil] the full file path name with the format extension
48
+ def for_format(format)
49
+ ext = @processor&.output_formats&.[](format)
50
+ ext ? with_extension(ext) : nil
51
+ end
52
+
53
+ # Returns the full file path name with the given extension
54
+ # @param ext [String] the extension
55
+ # @return [String] the full file path name with the extension
56
+ def with_extension(ext)
57
+ file = change_output_dir
58
+ "#{file}.#{ext}"
59
+ end
60
+
61
+ private
62
+
63
+ def change_output_dir
64
+ File.expand_path(if !@output_dir.nil?
65
+ File.join(
66
+ @output_dir,
67
+ File.basename(@noext_filename),
68
+ )
69
+ else
70
+ @noext_filename
71
+ end)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ class Compile
5
+ class OutputFilenameConfig
6
+ DEFAULT_TEMPLATE =
7
+ "{{ document.docidentifier | downcase" \
8
+ " | replace: '/' , '-'" \
9
+ " | replace: ' ' , '-' }}"
10
+
11
+ attr_reader :template
12
+
13
+ def initialize(template)
14
+ @template = if template.nil? || template.empty?
15
+ DEFAULT_TEMPLATE
16
+ else
17
+ template
18
+ end
19
+ end
20
+
21
+ def generate_filename(relaton_data)
22
+ template = Liquid::Template.parse(@template)
23
+ template.render("document" => relaton_data)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "liquid"
4
+
5
+ module Metanorma
6
+ class Compile
7
+ class RelatonDrop < Liquid::Drop
8
+ def initialize(relaton_data)
9
+ @relaton = relaton_data
10
+ end
11
+
12
+ def docidentifier
13
+ at("./docidentifier")
14
+ end
15
+
16
+ def title
17
+ at("./title")
18
+ end
19
+
20
+ def date
21
+ at("./date/on")
22
+ end
23
+
24
+ def publisher
25
+ at("./contributor[role/@type = 'publisher']/organization/name")
26
+ end
27
+
28
+ def language
29
+ at("./language")
30
+ end
31
+
32
+ def script
33
+ at("./script")
34
+ end
35
+
36
+ def version
37
+ at("./version")
38
+ end
39
+
40
+ def slugify
41
+ docidentifier&.downcase
42
+ &.gsub(/[^a-z0-9]+/, "-")
43
+ &.gsub(/-+/, "-")
44
+ &.gsub(/^-|-$/, "")
45
+ end
46
+
47
+ private
48
+
49
+ def at(xpath)
50
+ @relaton.at(xpath)&.text
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+