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
@@ -2,104 +2,176 @@ 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
+ content = read_file(filename)
43
+ Metanorma::Input::Asciidoc.new.extract_metanorma_options(content)
44
+ .merge(extract_xml_options(content))
45
+ end
46
+
47
+ def extract_flavor_options(options, options_in_file)
48
+ options[:type] ||= options_in_file[:type]
49
+ options[:type] = options[:type]&.to_sym
50
+ options[:supplied_type] = options[:type]
51
+ t = @registry.alias(options[:type]) and options[:type] = t
52
+ end
53
+
54
+ def extract_dir_options(options, options_in_file, filename)
55
+ filename.is_a?(Tempfile) and return
56
+ dir = filename.sub(%r(/[^/]+$), "/")
57
+ options_in_file[:relaton] and
58
+ options[:relaton] ||= File.join(dir,
59
+ options_in_file[:relaton])
60
+ options_in_file[:sourcecode] and
61
+ options[:sourcecode] ||= File.join(dir,
62
+ options_in_file[:sourcecode])
63
+ end
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
46
86
 
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
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
+
94
+ def get_extensions(options)
95
+ ext = extract_extensions(options)
96
+ !ext.include?(:presentation) && ext.any? do |e|
97
+ uses_presentation_xml?(e, options)
98
+ end and ext << :presentation
99
+ !ext.include?(:rxl) && options[:site_generate] and
100
+ ext << :rxl
101
+ ext
102
+ end
103
+
104
+ def extract_extensions(options)
105
+ formats = effective_output_formats(options)
106
+ options[:extension_keys] ||=
107
+ formats.reduce([]) { |memo, (k, _)| memo << k }
108
+ options[:extension_keys].reduce([]) do |memo, e|
109
+ if formats[e] then memo << e
110
+ else
111
+ unsupported_format_error(e)
112
+ memo
113
+ end
55
114
  end
56
115
  end
57
- end
58
116
 
59
- def font_install(opt)
60
- @fontist_installed or
61
- Util::FontistHelper.install_fonts(@processor, opt)
62
- @fontist_installed = true
63
- end
117
+ def font_install(opt)
118
+ @fontist_installed or
119
+ Util::FontistHelper.install_fonts(@processor, opt)
120
+ @fontist_installed = true
121
+ end
64
122
 
65
- private
123
+ private
66
124
 
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
125
+ def unsupported_format_error(ext)
126
+ message = "[metanorma] Error: #{ext} format is not supported " \
127
+ "for this standard."
128
+ @errors << message
129
+ Util.log(message, :error)
130
+ end
73
131
 
74
- def get_isodoc_options(file, options, ext)
75
- ret = @processor.extract_options(file)
76
- dir = options[:filename].sub(%r(/[^/]+$), "/")
77
- ret[:i18nyaml] &&= File.join(dir, ret[:i18nyaml])
78
- copy_isodoc_options_attrs(options, ret)
79
- font_manifest_mn2pdf(options, ret, ext)
80
- ret[:output_formats]&.select! do |k, _|
81
- options[:extension_keys].include?(k)
82
- end
83
- ret[:log] = @log
84
- ret
85
- end
132
+ def get_isodoc_options(file, options, ext)
133
+ ret = @processor.extract_options(file)
134
+ get_isodoc_fileparams(options, ret)
135
+ copy_isodoc_options_attrs(options, ret)
136
+ font_manifest_mn2pdf(options, ret, ext)
137
+ ret[:output_formats]&.select! do |k, _|
138
+ options[:extension_keys].include?(k)
139
+ end
140
+ ret[:log] = @log
141
+ ret
142
+ end
86
143
 
87
- def copy_isodoc_options_attrs(options, ret)
88
- ret[:datauriimage] = true if options[:datauriimage]
89
- ret[:sourcefilename] = options[:filename]
90
- %i(bare sectionsplit install_fonts baseassetpath aligncrosselements
91
- tocfigures toctables tocrecommendations strict)
92
- .each { |x| ret[x] ||= options[x] }
93
- end
144
+ def get_isodoc_fileparams(options, ret)
145
+ %i(i18nyaml relatonrenderconfig).each do |k|
146
+ dir = File.dirname(options[:filename])
147
+ ret[k] or next
148
+ (Pathname.new ret[k]).absolute? or
149
+ ret[k] = File.join(dir, ret[k])
150
+ end
151
+ end
152
+
153
+ def copy_isodoc_options_attrs(options, ret)
154
+ ret[:datauriimage] = true if options[:datauriimage]
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]
159
+ %i(bare sectionsplit sectionsplit_filename install_fonts baseassetpath
160
+ aligncrosselements tocfigures toctables tocrecommendations tocexamples
161
+ strict fonts)
162
+ .each { |x| ret[x] ||= options[x] }
163
+ end
94
164
 
95
- def font_manifest_mn2pdf(options, ret, ext)
96
- custom_fonts = Util::FontistHelper
97
- .has_custom_fonts?(@processor, options, ret)
165
+ def font_manifest_mn2pdf(options, ret, ext)
166
+ custom_fonts = Util::FontistHelper
167
+ .has_custom_fonts?(@processor, options, ret)
98
168
 
99
- ext == :pdf && custom_fonts and
100
- ret[:mn2pdf] = {
101
- font_manifest: Util::FontistHelper.location_manifest(@processor, ret),
102
- }
169
+ ext == :pdf && custom_fonts and
170
+ ret[:mn2pdf] = {
171
+ font_manifest: Util::FontistHelper
172
+ .location_manifest(@processor, ret),
173
+ }
174
+ end
103
175
  end
104
176
  end
105
177
  end
@@ -1,68 +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
- options[:relaton] or return
5
- xml = Nokogiri::XML(isodoc, &:huge)
6
- bibdata = xml.at("//bibdata") || xml.at("//xmlns:bibdata")
7
- File.open(options[:relaton], "w:UTF-8") { |f| f.write bibdata.to_xml }
8
- end
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
9
30
 
10
- def clean_sourcecode(xml)
11
- xml.xpath(".//callout | .//annotation | .//xmlns:callout | "\
12
- ".//xmlns:annotation").each(&:remove)
13
- xml.xpath(".//br | .//xmlns:br").each { |x| x.replace("\n") }
14
- a = xml.at("./body | ./xmlns:body") and xml = a
15
- HTMLEntities.new.decode(xml.children.to_xml)
16
- end
31
+ class << self
32
+ include Writeable
17
33
 
18
- def extract(isodoc, dirname, extract_types)
19
- dirname or return
20
- extract_types.nil? || extract_types.empty? and
21
- extract_types = %i[sourcecode image requirement]
22
- FileUtils.rm_rf dirname
23
- FileUtils.mkdir_p dirname
24
- xml = Nokogiri::XML(isodoc, &:huge)
25
- sourcecode_export(xml, dirname) if extract_types.include? :sourcecode
26
- image_export(xml, dirname) if extract_types.include? :image
27
- extract_types.include?(:requirement) and
28
- requirement_export(xml, dirname)
29
- end
34
+ private
30
35
 
31
- def sourcecode_export(xml, dirname)
32
- xml.at("//sourcecode | //xmlns:sourcecode") or return
33
- FileUtils.mkdir_p "#{dirname}/sourcecode"
34
- xml.xpath("//sourcecode | //xmlns:sourcecode").each_with_index do |s, i|
35
- filename = s["filename"] || sprintf("sourcecode-%04d.txt", i)
36
- export_output("#{dirname}/sourcecode/#{filename}",
37
- clean_sourcecode(s.dup))
38
- end
39
- end
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
40
45
 
41
- def image_export(xml, dirname)
42
- xml.at("//image | //xmlns:image") or return
43
- FileUtils.mkdir_p "#{dirname}/image"
44
- xml.xpath("//image | //xmlns:image").each_with_index do |s, i|
45
- next unless /^data:image/.match? s["src"]
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
46
55
 
47
- %r{^data:image/(?<imgtype>[^;]+);base64,(?<imgdata>.+)$} =~ s["src"]
48
- fn = s["filename"] || sprintf("image-%<num>04d.%<name>s",
49
- num: i, name: imgtype)
50
- export_output("#{dirname}/image/#{fn}", Base64.strict_decode64(imgdata),
51
- binary: true)
52
- end
53
- end
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"]
61
+
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
54
72
 
55
- REQUIREMENT_XPATH =
56
- "//requirement | //xmlns:requirement | //recommendation | "\
57
- "//xmlns:recommendation | //permission | //xmlns:permission".freeze
73
+ REQUIREMENT_XPATH =
74
+ "//requirement | //xmlns:requirement | //recommendation | "\
75
+ "//xmlns:recommendation | //permission | //xmlns:permission"
58
76
 
59
- def requirement_export(xml, dirname)
60
- xml.at(REQUIREMENT_XPATH) or return
61
- FileUtils.mkdir_p "#{dirname}/requirement"
62
- xml.xpath(REQUIREMENT_XPATH).each_with_index do |s, i|
63
- fn = s["filename"] ||
64
- sprintf("%<name>s-%<num>04d.xml", name: s.name, num: i)
65
- 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
66
86
  end
67
87
  end
68
88
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metanorma
4
+ class Compile
5
+ module Flavor
6
+ def load_flavor(stdtype)
7
+ Metanorma::Core::FlavorLoader.load_flavor(stdtype)
8
+ end
9
+
10
+ def taste2flavor(stdtype)
11
+ Metanorma::Core::FlavorLoader.taste2flavor(stdtype)
12
+ end
13
+
14
+ def stdtype2flavor_gem(stdtype)
15
+ Metanorma::Core::FlavorLoader.stdtype2flavor_gem(stdtype)
16
+ end
17
+ end
18
+ end
19
+ 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
+