Module: Ace::Assign::Atoms::PresetExpander

Defined in:
lib/ace/assign/atoms/preset_expander.rb

Overview

Pure functions for expanding preset templates into step definitions.

Handles ‘expansion:` directives in preset YAML files to generate hierarchical step structures from parameter arrays.

Supports:

  • ‘batch-parent`: Creates a parent container step that auto-completes

  • ‘foreach`: Iterates over array parameter to create child steps

  • ‘child-template`: Template for generating foreach children

Examples:

Preset with expansion

expansion:
  batch-parent:
    name: batch-tasks
    number: "010"
    instructions: "Batch container - auto-completes when children done."
  foreach: taskrefs
  child-template:
    name: "work-on-{{item}}"
    parent: "010"
    context: fork
    instructions: "Implement task {{item}}"

Generated steps for taskrefs: [148, 149, 150]

[
  { number: "010", name: "batch-tasks", instructions: "..." },
  { number: "010.01", name: "work-on-148", parent: "010", context: "fork", ... },
  { number: "010.02", name: "work-on-149", parent: "010", context: "fork", ... },
  { number: "010.03", name: "work-on-150", parent: "010", context: "fork", ... }
]

Class Method Summary collapse

Class Method Details

.expand(preset, parameters = {}) ⇒ Array<Hash>

Expand a preset configuration into concrete steps.

Parameters:

  • preset (Hash)

    Parsed preset YAML with optional expansion section

  • parameters (Hash) (defaults to: {})

    Parameter values including arrays for foreach

Returns:

  • (Array<Hash>)

    Expanded steps ready for job.yaml



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/ace/assign/atoms/preset_expander.rb', line 42

def self.expand(preset, parameters = {})
  parameters = normalize_taskref_alias(parameters)
  expansion = preset["expansion"]
  base_steps = preset["steps"] || []

  # If no expansion section, return base steps with parameter substitution
  unless expansion
    return base_steps.map { |step| substitute_parameters(step, parameters) }
  end

  expanded_steps = []

  # Process batch-parent if present
  if expansion["batch-parent"]
    parent_step = build_batch_parent(expansion["batch-parent"], parameters)
    expanded_steps << parent_step
  end

  # Process foreach expansion if present
  if expansion["foreach"] && expansion["child-template"]
    foreach_param = expansion["foreach"]
    items = normalize_array_parameter(parameters[foreach_param])

    unless items.empty?
      child_steps = build_foreach_children(
        expansion["child-template"],
        items,
        parameters
      )
      expanded_steps.concat(child_steps)
    end
  end

  # Add remaining base steps with parameter substitution
  base_steps.each do |step|
    expanded_step = substitute_parameters(step, parameters)
    expanded_steps << expanded_step
  end

  expanded_steps
end

.foreach_parameter(preset) ⇒ String?

Get the foreach parameter name from preset expansion.

Parameters:

  • preset (Hash)

    Preset configuration

Returns:

  • (String, nil)

    Name of the foreach parameter



150
151
152
# File 'lib/ace/assign/atoms/preset_expander.rb', line 150

def self.foreach_parameter(preset)
  preset.dig("expansion", "foreach")
end

.has_expansion?(preset) ⇒ Boolean

Check if a preset has expansion directives.

Parameters:

  • preset (Hash)

    Preset configuration

Returns:

  • (Boolean)

    True if preset uses expansion



142
143
144
# File 'lib/ace/assign/atoms/preset_expander.rb', line 142

def self.has_expansion?(preset)
  !preset["expansion"].nil?
end

.parse_array_parameter(value) ⇒ Array<String>

Parse array parameter from various input formats.

Parameters:

  • value (String, Array, nil)

    Parameter value

Returns:

  • (Array<String>)

    Normalized array of values



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/ace/assign/atoms/preset_expander.rb', line 88

def self.parse_array_parameter(value)
  return [] if value.nil?
  return value.map(&:to_s) if value.is_a?(Array)

  value_str = value.to_s.strip
  return [] if value_str.empty?

  # Check for range pattern (e.g., "148-152")
  if value_str.match?(/^\d+-\d+$/)
    start_num, end_num = value_str.split("-").map(&:to_i)
    return (start_num..end_num).map(&:to_s)
  end

  # Check for comma-separated values
  if value_str.include?(",")
    return value_str.split(",").map(&:strip)
  end

  # Check for pattern (contains * or ?)
  if value_str.match?(/[*?]/)
    # Return pattern as single-element array for later resolution
    return [value_str]
  end

  # Single value
  [value_str]
end

.validate_parameters(preset, parameters) ⇒ Array<String>

Validate preset parameters against requirements.

Parameters:

  • preset (Hash)

    Preset configuration

  • parameters (Hash)

    Provided parameter values

Returns:

  • (Array<String>)

    List of validation errors (empty if valid)



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/ace/assign/atoms/preset_expander.rb', line 121

def self.validate_parameters(preset, parameters)
  parameters = normalize_taskref_alias(parameters)
  errors = []
  param_defs = preset["parameters"] || {}

  param_defs.each do |name, config|
    next unless config["required"]

    value = parameters[name]
    if value.nil? || (value.respond_to?(:empty?) && value.empty?)
      errors << "Required parameter '#{name}' is missing"
    end
  end

  errors
end