Class: Ukiryu::Definition::DefinitionComposer

Inherits:
Object
  • Object
show all
Defined in:
lib/ukiryu/definition/definition_composer.rb

Overview

Compose tool definitions from multiple sources

This class handles merging and composing tool definitions, allowing for profile inheritance and command mixins.

Constant Summary collapse

MERGE_STRATEGIES =

Merge strategies

%i[override replace prepend append].freeze

Class Method Summary collapse

Class Method Details

.compose(definition, loader: nil) ⇒ Models::ToolDefinition

Compose a definition with its includes/inherits

Parameters:

  • definition (Models::ToolDefinition)

    the base definition

  • loader (Object) (defaults to: nil)

    the loader to use for loading dependencies

Returns:



18
19
20
21
22
23
24
25
26
27
28
# File 'lib/ukiryu/definition/definition_composer.rb', line 18

def self.compose(definition, loader: nil)
  @loader = loader || Loader

  # Process inherits first (base definitions)
  definition = process_inherits(definition) if definition.respond_to?(:inherits) && definition.inherits

  # Process includes (additions)
  definition = process_includes(definition) if definition.respond_to?(:includes) && definition.includes

  definition
end

.find_profile(profiles, name) ⇒ Object?

Find a profile by name

Parameters:

  • profiles (Array)

    the profiles array

  • name (String)

    the profile name

Returns:

  • (Object, nil)

    the profile or nil



222
223
224
225
226
# File 'lib/ukiryu/definition/definition_composer.rb', line 222

def self.find_profile(profiles, name)
  return nil unless profiles

  profiles.find { |p| p.name == name }
end

.load_base_definition(spec) ⇒ Models::ToolDefinition?

Load a base definition from a spec

Parameters:

  • spec (Hash, String)

    the specification

Returns:



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/ukiryu/definition/definition_composer.rb', line 68

def self.load_base_definition(spec)
  case spec
  when String
    # Just a tool name, find latest version
     = Discovery.find(spec)
    &.load_definition
  when Hash
    tool = spec[:tool] || spec['tool']
    version = spec[:version] || spec['version']

     = if version
                 Discovery.find(tool, version)
               else
                 Discovery.find(tool)
               end

    &.load_definition
  end
end

.merge_commands_by_name(base_commands, addition_commands) ⇒ Array

Merge commands by name

Parameters:

  • base_commands (Array)

    base commands

  • addition_commands (Array)

    commands to merge

Returns:

  • (Array)

    merged commands



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/ukiryu/definition/definition_composer.rb', line 199

def self.merge_commands_by_name(base_commands, addition_commands)
  merged = base_commands.dup

  addition_commands.each do |addition_command|
    existing_index = merged.find_index { |c| c.name == addition_command.name }

    if existing_index
      # Override existing command
      merged[existing_index] = addition_command.dup
    else
      # Add new command
      merged << addition_command.dup
    end
  end

  merged
end

.merge_definitions(base, addition, strategy: :override) ⇒ Models::ToolDefinition

Merge two definitions

Parameters:

Returns:



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/ukiryu/definition/definition_composer.rb', line 94

def self.merge_definitions(base, addition, strategy: :override)
  # Create a merged definition by deep copying and merging
  merged = base.dup

  # Merge profiles
  if addition.profiles
    merged.profiles ||= []

    case strategy
    when :replace
      merged.profiles = addition.profiles.dup
    when :override
      # Merge by profile name, addition overrides base
      merged.profiles = merge_profiles_by_name(base.profiles || [], addition.profiles)
    when :append
      # Append addition profiles
      merged.profiles = (base.profiles || []) + addition.profiles
    when :prepend
      # Prepend addition profiles
      merged.profiles = addition.profiles + (base.profiles || [])
    end
  end

  # Merge commands if profiles have them
  merged.profiles&.each do |profile|
    # Find corresponding profile in addition
    addition_profile = find_profile(addition.profiles, profile.name)
    next unless addition_profile

    # Merge commands
    next unless addition_profile.commands

    profile.commands ||= []
    case strategy
    when :replace
      profile.commands = addition_profile.commands.dup
    when :override
      profile.commands = merge_commands_by_name(profile.commands, addition_profile.commands)
    when :append
      profile.commands = profile.commands + addition_profile.commands
    when :prepend
      profile.commands = addition_profile.commands + profile.commands
    end
  end

  merged
end

.merge_profile_content(base, addition) ⇒ Object

Merge the content of two profiles

Parameters:

  • base (Object)

    the base profile

  • addition (Object)

    the profile to merge

Returns:

  • (Object)

    merged profile



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/ukiryu/definition/definition_composer.rb', line 171

def self.merge_profile_content(base, addition)
  # Create a new profile that merges both
  merged = base.dup

  # Merge commands
  merged.commands = merge_commands_by_name(base.commands || [], addition.commands) if addition.commands

  # Merge other arrays if they exist
  %i[environment options flags arguments].each do |attr|
    if addition.respond_to?(attr) && addition.send(attr)
      base_items = base.respond_to?(attr) ? base.send(attr) : []
      merged.send("#{attr}=", base_items + addition.send(attr))
    end
  end

  # Override scalars
  %i[description option_style].each do |attr|
    merged.send("#{attr}=", addition.send(attr)) if addition.respond_to?(attr) && !addition.send(attr).nil?
  end

  merged
end

.merge_profiles_by_name(base_profiles, addition_profiles) ⇒ Array

Merge profiles by name

Parameters:

  • base_profiles (Array)

    base profiles

  • addition_profiles (Array)

    profiles to merge

Returns:

  • (Array)

    merged profiles



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/ukiryu/definition/definition_composer.rb', line 147

def self.merge_profiles_by_name(base_profiles, addition_profiles)
  merged = base_profiles.dup

  addition_profiles.each do |addition_profile|
    existing_index = merged.find_index { |p| p.name == addition_profile.name }

    if existing_index
      # Merge the profile content
      existing = merged[existing_index]
      merged[existing_index] = merge_profile_content(existing, addition_profile)
    else
      # Add new profile
      merged << addition_profile.dup
    end
  end

  merged
end

.process_includes(definition) ⇒ Models::ToolDefinition

Process includes clauses

Parameters:

Returns:



51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/ukiryu/definition/definition_composer.rb', line 51

def self.process_includes(definition)
  includes = definition.includes || []
  return definition if includes.empty?

  # Load included definitions and merge them
  includes.reduce(definition) do |current, include_spec|
    include_def = load_base_definition(include_spec)
    next current unless include_def

    merge_definitions(current, include_def, strategy: :append)
  end
end

.process_inherits(definition) ⇒ Models::ToolDefinition

Process inherits clauses

Parameters:

Returns:



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/ukiryu/definition/definition_composer.rb', line 34

def self.process_inherits(definition)
  inherits = definition.inherits || []
  return definition if inherits.empty?

  # Load base definitions and merge them
  inherits.reverse.reduce(definition) do |current, inherit_spec|
    base_def = load_base_definition(inherit_spec)
    next current unless base_def

    merge_definitions(base_def, current, strategy: :replace)
  end
end

.validate_composable(definition) ⇒ Array<String>

Validate that a definition can be composed

Parameters:

Returns:

  • (Array<String>)

    array of validation errors (empty if valid)



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/ukiryu/definition/definition_composer.rb', line 232

def self.validate_composable(definition)
  errors = []

  # Check inherits
  if definition.respond_to?(:inherits) && definition.inherits
    definition.inherits.each do |inherit_spec|
      base_def = load_base_definition(inherit_spec)
      errors << "Cannot inherit from '#{inherit_spec}': definition not found" unless base_def
    end
  end

  # Check includes
  if definition.respond_to?(:includes) && definition.includes
    definition.includes.each do |include_spec|
      include_def = load_base_definition(include_spec)
      errors << "Cannot include '#{include_spec}': definition not found" unless include_def
    end
  end

  errors
end