metanorma-taste 0.0.4 → 0.1.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.
@@ -1,126 +1,339 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "yaml"
3
+ require_relative "taste_config"
4
4
 
5
5
  module Metanorma
6
6
  module Taste
7
+ # Base processor for taste-specific document attribute handling
8
+ #
9
+ # This class handles the processing of AsciiDoc attributes for taste-specific
10
+ # document generation, including file-based overrides, base configuration overrides,
11
+ # and doctype-specific transformations.
12
+ #
13
+ # @example Basic usage
14
+ # config = TasteConfig.from_yaml(yaml_content)
15
+ # base = Base.new("icc", config, directory: "data/icc")
16
+ #
17
+ # attrs = [":doctype: specification", ":title: My Document"]
18
+ # options = {}
19
+ # processed_attrs = base.process_input_adoc_overrides(attrs, options)
20
+ #
21
+ # @example Processing workflow
22
+ # # 1. File-based attributes are added (copyright, logo, i18n)
23
+ # # 2. Base override attributes are applied
24
+ # # 3. Doctype-specific transformations are performed
25
+ # # 4. Boilerplate authority is set in options if copyright exists
7
26
  class Base
8
- attr_reader :flavor, :config, :taste_info
27
+ # @return [String, Symbol] The flavor identifier (e.g., :icc, :elf)
28
+ attr_reader :flavor
9
29
 
10
- def initialize(flavor, taste_info)
30
+ # @return [TasteConfig] The taste configuration object
31
+ attr_reader :config
32
+
33
+ # Mapping of base override configuration keys to AsciiDoc attribute names
34
+ #
35
+ # This constant defines how base_override configuration properties
36
+ # are translated into AsciiDoc document attributes.
37
+ #
38
+ # @example Configuration to attribute mapping
39
+ # config.base_override.publisher => :publisher:
40
+ # config.base_override.presentation_metadata_color_secondary => :presentation-metadata-color-secondary:
41
+ BASE_OVERRIDE_MAPPINGS = {
42
+ publisher: "publisher",
43
+ publisher_abbr: "publisher_abbr",
44
+ presentation_metadata_color_secondary: "presentation-metadata-color-secondary",
45
+ presentation_metadata_backcover_text: "presentation-metadata-backcover-text",
46
+ body_font: "body-font",
47
+ header_font: "header-font",
48
+ monospace_font: "monospace-font",
49
+ }.freeze
50
+
51
+ # Initialize a new Base processor
52
+ #
53
+ # @param flavor [String, Symbol] The taste flavor identifier
54
+ # @param config [TasteConfig] The taste configuration object
55
+ # @param directory [String] The base directory for taste files (default: current directory)
56
+ #
57
+ # @example
58
+ # config = TasteConfig.from_yaml(File.read("config.yaml"))
59
+ # base = Base.new("icc", config, directory: "data/icc")
60
+ def initialize(flavor, config, directory: Dir.pwd)
11
61
  @flavor = flavor
12
- @taste_info = taste_info
13
- @config = taste_info[:config]
62
+ @config = config
63
+ @directory = directory
14
64
  end
15
65
 
66
+ # Process input AsciiDoc attributes with taste-specific overrides
67
+ #
68
+ # This is the main entry point for attribute processing. It applies
69
+ # file-based overrides, base configuration overrides, and doctype
70
+ # transformations to the input attributes.
71
+ #
72
+ # @param attrs [Array<String>] Array of AsciiDoc attribute strings
73
+ # @param options [Hash] Options hash that may be modified with additional settings
74
+ # @return [Array<String>] Modified array of AsciiDoc attributes
75
+ #
76
+ # @example
77
+ # attrs = [":doctype: specification", ":title: My Document"]
78
+ # options = {}
79
+ # result = base.process_input_adoc_overrides(attrs, options)
80
+ # # => [":doctype: specification", ":title: My Document", ":publisher: ICC", ...]
16
81
  def process_input_adoc_overrides(attrs, options)
17
- # Insert after the second element (index 1)
18
- # If attrs has fewer than 2 elements, this will handle it appropriately
19
- insertion_index = [attrs.length, 2].min
20
-
21
- attrs, new_attrs = build_attribute_overrides(attrs)
22
- attrs.insert(insertion_index, *new_attrs) unless new_attrs.empty?
82
+ # Insert new attributes after the second element (or at the end if fewer elements)
83
+ insertion_index = calculate_insertion_index(attrs)
23
84
 
24
- # Set boilerplate authority if copyright notice exists
25
- copyright_file = copyright_notice_path
26
- if copyright_file && File.exist?(copyright_file)
27
- options[":boilerplate-authority:"] = copyright_file
85
+ # Build and insert override attributes
86
+ attrs, override_attrs = build_all_attribute_overrides(attrs)
87
+ unless override_attrs.empty?
88
+ attrs.insert(insertion_index,
89
+ *override_attrs)
28
90
  end
29
91
 
92
+ # Set boilerplate authority in options if copyright notice exists
93
+ set_boilerplate_authority_option(options)
94
+
30
95
  attrs
31
96
  end
32
97
 
33
98
  private
34
99
 
35
- def build_attribute_overrides(attrs)
36
- overrides = []
37
- #build_attribute_copyright_overrides(overrides)
38
- #build_attribute_i18n_overrides(overrides)
39
- #build_attribute_publisher_logo_overrides(overrides)
40
- file_attr_override(:copyright_notice, "boilerplate-authority", overrides)
41
- file_attr_override(:publisher_logo, "publisher_logo", overrides)
42
- file_attr_override(:i18n_dictionary, "i18nyaml", overrides)
43
- # Add base-override attributes
44
- @taste_info[:base_override].each do |key, value|
45
- overrides << ":#{key}: #{value}"
46
- end
47
- build_attribute_doctype_overrides(attrs, overrides)
48
- [attrs, overrides]
100
+ # Calculate the appropriate insertion index for new attributes
101
+ #
102
+ # Attributes are inserted after the second element to maintain
103
+ # proper AsciiDoc document structure.
104
+ #
105
+ # @param attrs [Array] The attributes array
106
+ # @return [Integer] The insertion index
107
+ def calculate_insertion_index(attrs)
108
+ [attrs.length, 2].min
49
109
  end
50
110
 
51
- # Add copyright notice if available
52
- def build_attribute_copyright_overrides(overrides)
53
- copyright_file = copyright_notice_path
54
- if copyright_file && File.exist?(copyright_file)
55
- overrides << ":boilerplate-authority: #{copyright_file}"
56
- end
111
+ # Build all attribute overrides from various sources
112
+ #
113
+ # This method coordinates the building of attributes from:
114
+ # - File-based sources (copyright, logo, i18n)
115
+ # - Base override configuration
116
+ # - Doctype-specific overrides
117
+ #
118
+ # @param attrs [Array<String>] Original attributes array
119
+ # @return [Array<Array<String>, Array<String>>] Tuple of [original_attrs, override_attrs]
120
+ def build_all_attribute_overrides(attrs)
121
+ override_attrs = []
122
+
123
+ # Add attributes from different sources
124
+ add_file_based_overrides(override_attrs)
125
+ add_base_configuration_overrides(override_attrs)
126
+ apply_doctype_overrides(attrs, override_attrs)
127
+
128
+ [attrs, override_attrs]
57
129
  end
58
130
 
59
- # Add copyright notice if available
60
- def build_attribute_publisher_logo_overrides(overrides)
61
- copyright_file = publisher_logo_path
62
- if copyright_file && File.exist?(copyright_file)
63
- overrides << ":publisher_logo: #{copyright_file}"
131
+ # Add file-based attribute overrides
132
+ #
133
+ # Processes file-based configuration properties and adds corresponding
134
+ # attributes if the files exist.
135
+ #
136
+ # @param override_attrs [Array<String>] Array to append override attributes to
137
+ def add_file_based_overrides(override_attrs)
138
+ file_override_mappings.each do |config_attr, attr_name|
139
+ add_file_override(override_attrs, config_attr, attr_name)
64
140
  end
65
141
  end
66
142
 
67
- # Add i18n dictionary if available
68
- def build_attribute_i18n_overrides(overrides)
69
- i18n_file = i18n_dictionary_path
70
- if i18n_file && File.exist?(i18n_file)
71
- overrides << ":i18nyaml: #{i18n_file}"
72
- end
143
+ # Get the mapping of file-based configuration attributes to AsciiDoc attributes
144
+ #
145
+ # @return [Hash<Symbol, String>] Mapping of config attributes to AsciiDoc attribute names
146
+ def file_override_mappings
147
+ {
148
+ copyright_notice: "boilerplate-authority",
149
+ publisher_logo: "publisher_logo",
150
+ i18n_dictionary: "i18nyaml",
151
+ htmlcoverpage: "htmlcoverpage",
152
+ htmlintropage: "htmlintropage",
153
+ htmlstylesheet: "htmlstylesheet",
154
+ htmlstylesheet_override: "htmlstylesheet-override",
155
+ wordcoverpage: "wordcoverpage",
156
+ wordintropage: "wordintropage",
157
+ wordstylesheet: "wordstylesheet",
158
+ wordstylesheet_override: "wordstylesheet-override",
159
+ pdfstylesheet: "pdfstylesheet",
160
+ pdfstylesheet_override: "pdfstylesheet-override",
161
+ }
162
+ end
163
+
164
+ # Add a single file-based override attribute
165
+ #
166
+ # @param override_attrs [Array<String>] Array to append to
167
+ # @param config_attr [Symbol] Configuration attribute name
168
+ # @param attr_name [String] AsciiDoc attribute name
169
+ def add_file_override(override_attrs, config_attr, attr_name)
170
+ filepath = file_path_for(config_attr)
171
+ return unless filepath && File.exist?(filepath)
172
+
173
+ override_attrs << ":#{attr_name}: #{filepath}"
174
+ end
175
+
176
+ # Get the full file path for a configuration attribute
177
+ #
178
+ # @param config_attr [Symbol] The configuration attribute name
179
+ # @return [String, nil] The full file path, or nil if not configured
180
+ #
181
+ # @example
182
+ # file_path_for(:copyright_notice) # => "data/icc/copyright.adoc"
183
+ # file_path_for(:publisher_logo) # => "data/icc/logo.svg"
184
+ def file_path_for(config_attr)
185
+ filename = @config.send(config_attr)
186
+ return nil unless filename
187
+
188
+ File.join(@directory, filename)
73
189
  end
74
190
 
75
- def file_attr_override(source_attr_name, target_attr_name, overrides)
76
- s = @taste_info[source_attr_name] or return nil
77
- f = File.join(@taste_info[:directory], s)
78
- if f && File.exist?(f)
79
- overrides << ":#{target_attr_name}: #{f}"
191
+ # Add base configuration override attributes
192
+ #
193
+ # Processes base_override configuration and adds corresponding
194
+ # AsciiDoc attributes for each configured property.
195
+ #
196
+ # @param override_attrs [Array<String>] Array to append override attributes to
197
+ def add_base_configuration_overrides(override_attrs)
198
+ return unless @config.base_override
199
+
200
+ BASE_OVERRIDE_MAPPINGS.each do |config_key, attr_key|
201
+ value = @config.base_override.send(config_key)
202
+ next unless value
203
+
204
+ override_attrs << ":#{attr_key}: #{value}"
80
205
  end
81
206
  end
82
207
 
83
- def build_attribute_doctype_overrides(attrs, overrides)
84
- doctype_idx, dt = build_attribute_doctype_overrides_prep(attrs)
85
- dt.nil? and return
86
- overrides << ":presentation-metadata-doctype-alias: #{dt['taste']}"
87
- attrs[doctype_idx] = ":doctype: #{dt['base']}"
88
- dt["override_attributes"]&.each do |key, value|
89
- overrides << ":#{key}: #{value}"
208
+ # Apply doctype-specific overrides and transformations
209
+ #
210
+ # This method handles the transformation of doctype attributes based on
211
+ # taste-specific doctype configurations. It:
212
+ # 1. Finds the doctype attribute in the input
213
+ # 2. Looks up the corresponding doctype configuration
214
+ # 3. Transforms the doctype value (taste -> base)
215
+ # 4. Adds doctype-specific override attributes
216
+ #
217
+ # @param attrs [Array<String>] The attributes array (modified in place)
218
+ # @param override_attrs [Array<String>] Array to append override attributes to
219
+ def apply_doctype_overrides(attrs, override_attrs)
220
+ doctype_index = find_doctype_attribute_index(attrs)
221
+ return unless doctype_index
222
+
223
+ current_doctype = extract_doctype_value(attrs[doctype_index])
224
+ doctype_config = find_doctype_configuration(current_doctype)
225
+ return unless doctype_config
226
+
227
+ # Transform the doctype attribute
228
+ transform_doctype_attribute(attrs, doctype_index, doctype_config)
229
+
230
+ # Add doctype-specific overrides
231
+ add_doctype_specific_overrides(override_attrs, doctype_config)
232
+ end
233
+
234
+ # Find the index of the doctype attribute in the attributes array
235
+ #
236
+ # @param attrs [Array<String>] The attributes array
237
+ # @return [Integer, nil] The index of the doctype attribute, or nil if not found
238
+ def find_doctype_attribute_index(attrs)
239
+ attrs.index { |attr| attr.match?(/^:doctype:/) }
240
+ end
241
+
242
+ # Extract the doctype value from a doctype attribute string
243
+ #
244
+ # @param doctype_attr [String] The doctype attribute string (e.g., ":doctype: specification")
245
+ # @return [String] The doctype value (e.g., "specification")
246
+ def extract_doctype_value(doctype_attr)
247
+ doctype_attr.sub(/^:doctype:/, "").strip
248
+ end
249
+
250
+ # Find the doctype configuration for a given taste doctype
251
+ #
252
+ # @param taste_doctype [String] The taste-specific doctype name
253
+ # @return [DoctypeConfig, nil] The doctype configuration, or nil if not found
254
+ def find_doctype_configuration(taste_doctype)
255
+ @config.doctypes&.detect { |dt| dt.taste == taste_doctype }
256
+ end
257
+
258
+ # Transform the doctype attribute from taste-specific to base doctype
259
+ #
260
+ # @param attrs [Array<String>] The attributes array (modified in place)
261
+ # @param doctype_index [Integer] The index of the doctype attribute
262
+ # @param doctype_config [DoctypeConfig] The doctype configuration
263
+ def transform_doctype_attribute(attrs, doctype_index, doctype_config)
264
+ attrs[doctype_index] = ":doctype: #{doctype_config.base}"
265
+ end
266
+
267
+ # Add doctype-specific override attributes
268
+ #
269
+ # @param override_attrs [Array<String>] Array to append override attributes to
270
+ # @param doctype_config [DoctypeConfig] The doctype configuration
271
+ def add_doctype_specific_overrides(override_attrs, doctype_config)
272
+ # Add the doctype alias for presentation metadata
273
+ override_attrs << ":presentation-metadata-doctype-alias: #{doctype_config.taste}"
274
+
275
+ # Add any additional override attributes defined in the doctype configuration
276
+ add_doctype_override_attributes(override_attrs, doctype_config)
277
+ end
278
+
279
+ # Add override attributes defined in the doctype configuration
280
+ #
281
+ # The override_attributes property contains an array of hash objects,
282
+ # where each hash contains key-value pairs for attribute overrides.
283
+ #
284
+ # @param override_attrs [Array<String>] Array to append override attributes to
285
+ # @param doctype_config [DoctypeConfig] The doctype configuration
286
+ def add_doctype_override_attributes(override_attrs, doctype_config)
287
+ return unless doctype_config.override_attributes
288
+
289
+ doctype_config.override_attributes.each do |attr_hash|
290
+ attr_hash.each do |key, value|
291
+ override_attrs << ":#{key}: #{value}"
292
+ end
90
293
  end
91
294
  end
92
295
 
93
- def build_attribute_doctype_overrides_prep(attrs)
94
- doctype_idx = attrs.index { |e| /^:doctype:/.match?(e) } or
95
- return [nil, nil, nil]
96
- old_doctype = attrs[doctype_idx].sub(/^:doctype:/, "").strip
97
- dt = @taste_info[:doctypes].detect do |e|
98
- e["taste"] == old_doctype
99
- end or return [nil, nil, nil]
100
- [doctype_idx, dt]
296
+ # Set the boilerplate authority option if copyright notice exists
297
+ #
298
+ # This method checks for the existence of a copyright notice file
299
+ # and sets the appropriate option for boilerplate processing.
300
+ #
301
+ # @param options [Hash] Options hash to modify
302
+ def set_boilerplate_authority_option(options)
303
+ copyright_file = copyright_notice_path
304
+ return unless copyright_file && File.exist?(copyright_file)
305
+
306
+ options[":boilerplate-authority:"] = copyright_file
101
307
  end
102
308
 
309
+ # Get the full path to the copyright notice file
310
+ #
311
+ # @return [String, nil] The copyright notice file path, or nil if not configured
312
+ #
313
+ # @example
314
+ # copyright_notice_path # => "data/icc/copyright.adoc"
103
315
  def copyright_notice_path
104
- @taste_info[:copyright_notice] or return nil
105
- File.join(@taste_info[:directory], @taste_info[:copyright_notice])
316
+ file_path_for(:copyright_notice)
106
317
  end
107
318
 
319
+ # Get the full path to the publisher logo file
320
+ #
321
+ # @return [String, nil] The publisher logo file path, or nil if not configured
322
+ #
323
+ # @example
324
+ # publisher_logo_path # => "data/icc/logo.svg"
108
325
  def publisher_logo_path
109
- @taste_info[:publisher_logo] or return nil
110
- File.join(@taste_info[:directory], @taste_info[:publisher_logo])
326
+ file_path_for(:publisher_logo)
111
327
  end
112
328
 
329
+ # Get the full path to the i18n dictionary file
330
+ #
331
+ # @return [String, nil] The i18n dictionary file path, or nil if not configured
332
+ #
333
+ # @example
334
+ # i18n_dictionary_path # => "data/icc/i18n.yaml"
113
335
  def i18n_dictionary_path
114
- @taste_info[:i18n_dictionary] or return nil
115
- File.join(@taste_info[:directory], @taste_info[:i18n_dictionary])
116
- end
117
-
118
- def load_i18n_dictionary
119
- i18n_file = i18n_dictionary_path
120
- i18n_file && File.exist?(i18n_file) or return {}
121
- YAML.load_file(i18n_file)
122
- rescue StandardError
123
- {}
336
+ file_path_for(:i18n_dictionary)
124
337
  end
125
338
  end
126
339
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Metanorma
6
+ module Taste
7
+ # Model for base-override configuration
8
+ class BaseOverride < Lutaml::Model::Serializable
9
+ attribute :publisher, :string
10
+ attribute :publisher_abbr, :string
11
+ attribute :body_font, :string
12
+ attribute :header_font, :string
13
+ attribute :monospace_font, :string
14
+ attribute :presentation_metadata_color_secondary, :string
15
+ attribute :presentation_metadata_backcover_text, :string
16
+
17
+ key_value do
18
+ map "publisher", to: :publisher
19
+ map "publisher_abbr", to: :publisher_abbr
20
+ map "body-font", to: :body_font
21
+ map "header-font", to: :header_font
22
+ map "monospace-font", to: :monospace_font
23
+ map "presentation-metadata-color-secondary",
24
+ to: :presentation_metadata_color_secondary
25
+ map "presentation-metadata-backcover-text",
26
+ to: :presentation_metadata_backcover_text
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+
5
+ module Metanorma
6
+ module Taste
7
+ # Model for individual doctype configuration
8
+ class DoctypeConfig < Lutaml::Model::Serializable
9
+ attribute :taste, :string
10
+ attribute :base, :string
11
+ attribute :override_attributes, :hash, collection: true
12
+
13
+ key_value do
14
+ map "taste", to: :taste
15
+ map "base", to: :base
16
+ map "override-attributes", to: :override_attributes
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lutaml/model"
4
+ require_relative "base_override"
5
+ require_relative "doctype_config"
6
+
7
+ module Metanorma
8
+ module Taste
9
+ # Main configuration model
10
+ class TasteConfig < Lutaml::Model::Serializable
11
+ attribute :flavor, :string
12
+ attribute :owner, :string
13
+ attribute :base_flavor, :string
14
+ attribute :copyright_notice, :string
15
+ attribute :i18n_dictionary, :string
16
+ attribute :publisher_logo, :string
17
+ attribute :htmlcoverpage, :string
18
+ attribute :htmlintropage, :string
19
+ attribute :htmlstylesheet, :string
20
+ attribute :htmlstylesheet_override, :string
21
+ attribute :wordcoverpage, :string
22
+ attribute :wordintropage, :string
23
+ attribute :wordstylesheet, :string
24
+ attribute :wordstylesheet_override, :string
25
+ attribute :pdfstylesheet, :string
26
+ attribute :pdfstylesheet_override, :string
27
+ attribute :base_override, BaseOverride
28
+ attribute :doctypes, DoctypeConfig, collection: true
29
+ attribute :directory, :string
30
+
31
+ key_value do
32
+ map "flavor", to: :flavor
33
+ map "owner", to: :owner
34
+ map "base-flavor", to: :base_flavor
35
+ map "copyright-notice", to: :copyright_notice
36
+ map "i18n-dictionary", to: :i18n_dictionary
37
+ map "publisher-logo", to: :publisher_logo
38
+ map "htmlcoverpage", to: :htmlcoverpage
39
+ map "htmlintropage", to: :htmlintropage
40
+ map "htmlstylesheet", to: :htmlstylesheet
41
+ map "htmlstylesheet-override", to: :htmlstylesheet_override
42
+ map "wordcoverpage", to: :wordcoverpage
43
+ map "wordintropage", to: :wordintropage
44
+ map "wordstylesheet", to: :wordstylesheet
45
+ map "wordstylesheet-override", to: :wordstylesheet_override
46
+ map "pdfstylesheet", to: :pdfstylesheet
47
+ map "pdfstylesheet-override", to: :pdfstylesheet_override
48
+ map "base-override", to: :base_override
49
+ map "doctypes", to: :doctypes
50
+ end
51
+ end
52
+ end
53
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Metanorma
4
4
  module Taste
5
- VERSION = "0.0.4"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end