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