Class: Ace::Bundle::Molecules::SectionProcessor

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/bundle/molecules/section_processor.rb

Overview

Processes and manages section definitions and composition

Instance Method Summary collapse

Constructor Details

#initializeSectionProcessor

Returns a new instance of SectionProcessor.



11
12
13
# File 'lib/ace/bundle/molecules/section_processor.rb', line 11

def initialize
  @validator = Atoms::SectionValidator.new
end

Instance Method Details

#filter_sections_by_type(sections, content_type) ⇒ Hash

Filters sections by content type (based on actual content, not content_type field)

Parameters:

  • sections (Hash)

    sections hash

  • content_type (String)

    content type to filter by (files, commands, diffs, content)

Returns:

  • (Hash)

    filtered sections



88
89
90
91
92
# File 'lib/ace/bundle/molecules/section_processor.rb', line 88

def filter_sections_by_type(sections, content_type)
  sections.select { |_, section|
    has_content_type?(section, content_type)
  }
end

#get_section_names_by_type(sections, content_type) ⇒ Array<String>

Gets section names for a content type

Parameters:

  • sections (Hash)

    sections hash

  • content_type (String)

    content type

Returns:

  • (Array<String>)

    section names



122
123
124
# File 'lib/ace/bundle/molecules/section_processor.rb', line 122

def get_section_names_by_type(sections, content_type)
  filter_sections_by_type(sections, content_type).keys
end

#has_content_type?(section, content_type) ⇒ Boolean

Public access for testing content type detection

Parameters:

  • section (Hash)

    section definition

  • content_type (String)

    content type to check for

Returns:

  • (Boolean)

    true if section has the specified content type



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/ace/bundle/molecules/section_processor.rb', line 248

def has_content_type?(section, content_type)
  case content_type
  when "files"
    !!(section["files"] || section[:files])
  when "commands"
    !!(section["commands"] || section[:commands])
  when "diffs"
    !!(section["ranges"] || section[:ranges] || section["diffs"] || section[:diffs])
  when "presets"
    !!(section["presets"] || section[:presets])
  when "content"
    !!(section["content"] || section[:content])
  else
    false
  end
end

#has_sections?(config) ⇒ Boolean

Checks if configuration already has sections

Parameters:

  • config (Hash)

    configuration to check

Returns:

  • (Boolean)

    true if has sections



39
40
41
42
43
44
45
46
# File 'lib/ace/bundle/molecules/section_processor.rb', line 39

def has_sections?(config)
  # Use 'bundle' key for configuration
  bundle = config["bundle"] || config[:bundle]
  return false unless bundle

  sections = bundle["sections"] || bundle[:sections]
  sections && !sections.empty?
end

#merge_preset_content(*preset_contents) ⇒ Hash

Merges preset content structures (similar to PresetManager but focused on bundles)

Parameters:

  • preset_contents (Array<Hash>)

    array of preset content hashes

Returns:

  • (Hash)

    merged preset content



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/ace/bundle/molecules/section_processor.rb', line 193

def merge_preset_content(*preset_contents)
  return {} if preset_contents.empty?
  return preset_contents.first if preset_contents.size == 1

  merged = {
    "files" => [],
    "commands" => [],
    "ranges" => [],
    "diffs" => [],
    "sections" => {},
    "content" => ""
  }

  preset_contents.each do |content|
    next unless content

    # Merge arrays (concatenate and deduplicate)
    %w[files commands ranges diffs].each do |array_key|
      if content[array_key]&.any?
        merged[array_key].concat(content[array_key])
        merged[array_key].uniq!
      end
    end

    # Merge sections
    if content["sections"]&.any?
      content["sections"].each do |section_name, section_data|
        merged["sections"][section_name] = if merged["sections"].key?(section_name)
          merge_section_data(
            merged["sections"][section_name],
            section_data
          )
        else
          deep_copy(section_data)
        end
      end
    end

    # Concatenate content
    if content["content"] && !content["content"].empty?
      if merged["content"].empty?
        merged["content"] = content["content"]
      else
        merged["content"] += "\n\n#{content["content"]}"
      end
    end
  end

  merged
end

#merge_preset_content_into_section(section_data, preset_content) ⇒ Hash

Merges preset content into a section

Parameters:

  • section_data (Hash)

    original section data

  • preset_content (Hash)

    merged preset content

Returns:

  • (Hash)

    section with preset content merged



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/ace/bundle/molecules/section_processor.rb', line 157

def merge_preset_content_into_section(section_data, preset_content)
  merged = deep_copy(section_data)

  # Merge content from preset bundle (handle both string and symbol keys)
  %w[files commands ranges diffs].each do |content_type|
    preset_files = preset_content[content_type] || preset_content[content_type.to_sym]
    if preset_files&.any?
      # Get existing files from both string and symbol keys
      existing_files = merged[content_type] || merged[content_type.to_sym] || []
      merged[content_type] = (existing_files + preset_files).uniq
    end
  end

  # Merge sections from preset bundle (flatten into current section)
  if preset_content["sections"]&.any?
    preset_content["sections"].each do |_, preset_section|
      merged = merge_section_data(merged, preset_section)
    end
  end

  # Merge other content
  if preset_content["content"] && !preset_content["content"].empty?
    existing_content = merged["content"] || merged[:content] || ""
    merged["content"] = if existing_content.empty?
      preset_content["content"]
    else
      existing_content + "\n\n#{preset_content["content"]}"
    end
  end

  merged
end

#merge_section_presets(preset_names, preset_manager, section_name) ⇒ Hash

Merges multiple presets for use within a section

Parameters:

  • preset_names (Array<String>)

    array of preset names

  • preset_manager (PresetManager)

    preset manager

  • section_name (String)

    section name for error reporting

Returns:

  • (Hash)

    merged preset content



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/ace/bundle/molecules/section_processor.rb', line 131

def merge_section_presets(preset_names, preset_manager, section_name)
  all_presets = []
  errors = []

  preset_names.each do |preset_name|
    preset = preset_manager.load_preset_with_composition(preset_name)
    if preset[:success]
      all_presets << preset
    else
      errors << "Failed to load preset '#{preset_name}' for section '#{section_name}': #{preset[:error]}"
    end
  end

  if errors.any?
    raise Ace::Bundle::SectionValidationError, "Section preset loading failed for section '#{section_name}':\n  #{errors.join("\n  ")}\n\nPlease ensure all referenced presets exist and are accessible. Check preset names for typos and verify preset files are in the correct location."
  end

  # Extract bundle content from presets
  preset_contents = all_presets.map { |preset| preset[:bundle] || {} }
  merge_preset_content(*preset_contents)
end

#merge_sections(*sections_list) ⇒ Hash

Merges sections from multiple configurations

Parameters:

  • sections_list (Array<Hash>)

    list of sections hashes

Returns:

  • (Hash)

    merged sections



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/ace/bundle/molecules/section_processor.rb', line 51

def merge_sections(*sections_list)
  merged = {}

  sections_list.compact.each do |sections|
    next if sections.empty?

    sections.each do |name, section|
      merged[name] = if merged.key?(name)
        merge_section_data(merged[name], section)
      else
        deep_copy(section)
      end
    end
  end

  # Validate final merged sections
  unless @validator.validate_sections(merged)
    errors = @validator.errors
    raise Ace::Bundle::SectionValidationError, "Merged sections validation failed after processing:\n  #{errors.join("\n  ")}\n\nThis error occurred after merging preset content. Please check if referenced presets are compatible."
  end

  merged
end

#process_section_presets(sections, preset_manager) ⇒ Hash

Processes preset references within sections

Parameters:

  • sections (Hash)

    sections hash

  • preset_manager (PresetManager)

    preset manager for loading referenced presets

Returns:

  • (Hash)

    sections with preset content merged in



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/ace/bundle/molecules/section_processor.rb', line 98

def process_section_presets(sections, preset_manager)
  processed = deep_copy(sections)

  processed.each do |section_name, section_data|
    presets = section_data[:presets] || section_data["presets"]
    next unless presets&.any?

    # Load all referenced presets
    merged_preset_content = merge_section_presets(presets, preset_manager, section_name)

    # Merge preset content into section
    processed[section_name] = merge_preset_content_into_section(section_data, merged_preset_content)

    # Remove the presets reference after processing (normalized to symbol)
    processed[section_name].delete(:presets)
  end

  processed
end

#process_sections(config, preset_manager = nil) ⇒ Hash

Processes section definitions from configuration

Parameters:

  • config (Hash)

    configuration hash containing sections

  • preset_manager (PresetManager) (defaults to: nil)

    preset manager for loading referenced presets

Returns:

  • (Hash)

    processed sections hash



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/ace/bundle/molecules/section_processor.rb', line 19

def process_sections(config, preset_manager = nil)
  sections_config = extract_sections_config(config)
  return {} if sections_config.nil? || sections_config.empty?

  unless @validator.validate_sections(sections_config)
    errors = @validator.errors
    raise Ace::Bundle::SectionValidationError, "Section validation failed:\n  #{errors.join("\n  ")}\n\nPlease check your sections configuration and ensure all required fields are properly formatted."
  end

  processed_sections = normalize_sections(sections_config)

  # Process preset references within sections if preset manager is available
  if preset_manager
    process_section_presets(processed_sections, preset_manager)
  end
end

#sorted_sections(sections) ⇒ Array

Gets sections sorted by YAML insertion order

Parameters:

  • sections (Hash)

    sections hash

Returns:

  • (Array)

    array of [name, section] pairs in YAML order



78
79
80
81
82
# File 'lib/ace/bundle/molecules/section_processor.rb', line 78

def sorted_sections(sections)
  # In Ruby 3.2+, hash insertion order is preserved
  # This returns sections in the order they appear in the YAML file
  sections.to_a
end