Module: Ukiryu::OptionsBuilder

Defined in:
lib/ukiryu/options_builder.rb,
lib/ukiryu/options_builder/formatter.rb,
lib/ukiryu/options_builder/validator.rb

Overview

Builds structured option classes from tool profile metadata

This module dynamically creates option classes based on YAML profile definitions, providing type-safe option building with shell serialization capabilities.

Examples:

# Get options class for a command
options_class = Ukiryu::OptionsBuilder.for(:imagemagick, :convert)
options = options_class.new
options.inputs = ["input.png"]
options.output = "output.jpg"
options.resize = "50x50"

# Serialize to shell command
shell_args = options.to_shell(type: :bash)

Defined Under Namespace

Modules: Formatter, Validator

Class Method Summary collapse

Class Method Details

.clear_cacheObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Clear the options class cache (mainly for testing)



61
62
63
# File 'lib/ukiryu/options_builder.rb', line 61

def clear_cache
  option_classes_cache.clear
end

.create_options_class(tool_name, command_name, command_def) ⇒ Class

Create a dynamic options class from command definition

Parameters:

  • tool_name (Symbol)

    the tool name

  • command_name (Symbol)

    the command name

  • command_def (Hash)

    the command definition from profile

Returns:

  • (Class)

    the generated options class



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/ukiryu/options_builder.rb', line 117

def create_options_class(tool_name, command_name, command_def)
  # Capture values in closure for singleton methods
  cmd_def = command_def
  t_name = tool_name
  c_name = command_name

  Class.new do
    # Define class methods with closure access
    singleton_class.send(:define_method, :command_def) do
      cmd_def
    end

    singleton_class.send(:define_method, :tool_name) do
      t_name
    end

    singleton_class.send(:define_method, :command_name) do
      c_name
    end

    # Define attribute accessors for each argument and option
    Ukiryu::OptionsBuilder.define_accessors(self, command_def)

    # Define to_shell method for serialization
    Ukiryu::OptionsBuilder.define_to_shell_method(self, command_def)

    # Define validation method
    Ukiryu::OptionsBuilder::Validator.define_validation_method(self, command_def)
  end
end

.define_accessors(klass, command_def) ⇒ Object

Define attribute accessors for arguments and options

Parameters:

  • klass (Class)

    the class to define accessors on

  • command_def (CommandDefinition)

    the command definition



152
153
154
155
156
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/ukiryu/options_builder.rb', line 152

def define_accessors(klass, command_def)
  # Define accessors for arguments
  (command_def.arguments || []).each do |arg_def|
    attr_name = arg_def.name
    # Create getter and setter
    klass.define_method(attr_name) do
      instance_variable_get("@#{attr_name}")
    end

    klass.define_method("#{attr_name}=") do |value|
      # For variadic arguments, validate each element
      validated = if arg_def.variadic && value.is_a?(Array)
                    value.map { |v| Ukiryu::Type.validate(v, arg_def.type || :string, arg_def) }
                  else
                    Ukiryu::Type.validate(value, arg_def.type || :string, arg_def)
                  end
      instance_variable_set("@#{attr_name}", validated)
    end
  end

  # Define accessors for options
  (command_def.options || []).each do |opt_def|
    attr_name = opt_def.name
    klass.define_method(attr_name) do
      instance_variable_get("@#{attr_name}")
    end

    klass.define_method("#{attr_name}=") do |value|
      # Skip if nil (optional option not set)
      return if value.nil?

      # Validate and coerce the value
      validated = Ukiryu::Type.validate(value, opt_def.type || :string, opt_def)
      instance_variable_set("@#{attr_name}", validated)
    end
  end

  # Define accessors for flags
  (command_def.flags || []).each do |flag_def|
    attr_name = flag_def.name
    klass.define_method(attr_name) do
      instance_variable_get("@#{attr_name}")
    end

    klass.define_method("#{attr_name}=") do |value|
      instance_variable_set("@#{attr_name}", !!value)
    end
  end

  # Define accessors for post_options
  (command_def.post_options || []).each do |opt_def|
    attr_name = opt_def.name
    klass.define_method(attr_name) do
      instance_variable_get("@#{attr_name}")
    end

    klass.define_method("#{attr_name}=") do |value|
      return if value.nil?

      validated = Ukiryu::Type.validate(value, opt_def.type || :string, opt_def)
      instance_variable_set("@#{attr_name}", validated)
    end
  end
end

.define_to_shell_method(klass, command_def) ⇒ Object

Define to_shell method for command serialization

Parameters:

  • klass (Class)

    the class to define the method on

  • command_def (CommandDefinition)

    the command definition



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/ukiryu/options_builder.rb', line 221

def define_to_shell_method(klass, command_def)
  klass.define_method(:to_shell) do |shell_type: :bash|
    shell_type = shell_type.to_sym
    shell_class = Ukiryu::Shell.class_for(shell_type)
    shell_instance = shell_class.new

    args = []

    # Add subcommand if present
    args << command_def.subcommand if command_def.subcommand

    # Add options (before arguments)
    (command_def.options || []).each do |opt_def|
      attr_name = opt_def.name
      value = instance_variable_get("@#{attr_name}")
      next if value.nil? # Skip unset options

      formatted = Ukiryu::OptionsBuilder::Formatter.format_option(opt_def, value, shell_instance)
      Array(formatted).each { |a| args << a unless a.nil? || a.empty? }
    end

    # Add flags
    (command_def.flags || []).each do |flag_def|
      attr_name = flag_def.name
      value = instance_variable_get("@#{attr_name}")

      # Use default if not set
      value = flag_def.default if value.nil?
      next unless value

      formatted = Ukiryu::OptionsBuilder::Formatter.format_flag(flag_def, shell_instance)
      Array(formatted).each { |f| args << f unless f.nil? || f.empty? }
    end

    # Separate "last" positioned argument from other arguments
    arguments = command_def.arguments || []
    last_arg = arguments.find { |a| ['last', :last].include?(a.position) }
    regular_args = arguments.reject { |a| ['last', :last].include?(a.position) }

    # Add regular positional arguments (in order)
    regular_args.sort_by do |a|
      pos = a.position
      pos.is_a?(Integer) ? pos : (pos || 99)
    end.each do |arg_def|
      attr_name = arg_def.name
      value = instance_variable_get("@#{attr_name}")
      next if value.nil?

      if arg_def.variadic
        Array(value).each do |v|
          args << Ukiryu::OptionsBuilder::Formatter.format_arg(v, arg_def, shell_instance)
        end
      else
        args << Ukiryu::OptionsBuilder::Formatter.format_arg(value, arg_def, shell_instance)
      end
    end

    # Add post_options (between regular args and last arg)
    (command_def.post_options || []).each do |opt_def|
      attr_name = opt_def.name
      value = instance_variable_get("@#{attr_name}")
      next if value.nil?

      formatted = Ukiryu::OptionsBuilder::Formatter.format_option(opt_def, value, shell_instance)
      Array(formatted).each { |a| args << a unless a.nil? || a.empty? }
    end

    # Add the "last" positioned argument (typically output)
    if last_arg
      attr_name = last_arg.name
      value = instance_variable_get("@#{attr_name}")
      if value
        if last_arg.variadic
          Array(value).each do |v|
            args << Ukiryu::OptionsBuilder::Formatter.format_arg(v, last_arg, shell_instance)
          end
        else
          args << Ukiryu::OptionsBuilder::Formatter.format_arg(value, last_arg, shell_instance)
        end
      end
    end

    # Join into command string
    shell_instance.join(*args)
  end
end

.for(tool_name, command_name) ⇒ Class

Get or create an options class for a tool command

Parameters:

  • tool_name (String, Symbol)

    the tool name

  • command_name (String, Symbol)

    the command name

Returns:

  • (Class)

    the dynamically generated options class

Raises:

  • (ArgumentError)


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/ukiryu/options_builder.rb', line 37

def for(tool_name, command_name)
  tool_name_sym = tool_name.to_sym
  command_name_sym = command_name.to_sym

  # Check if we've already created this class
  cache_key = [tool_name_sym, command_name_sym]
  cached = option_classes_cache[cache_key]
  return cached if cached

  # Get the tool and command profile
  tool = Ukiryu::Tool.get(tool_name_sym)
  command_def = tool.command_definition(command_name_sym)

  raise ArgumentError, "Unknown command: #{command_name} for tool: #{tool_name}" unless command_def

  # Create the options class
  options_class = create_options_class(tool_name_sym, command_name_sym, command_def)
  option_classes_cache[cache_key] = options_class
  options_class
end

.option_classes_cacheCache

Get the options classes cache (bounded LRU cache)

Returns:

  • (Cache)

    the options classes cache



28
29
30
# File 'lib/ukiryu/options_builder.rb', line 28

def option_classes_cache
  @option_classes_cache ||= Ukiryu::Cache.new(max_size: 100, ttl: 3600)
end

.to_hash(options) ⇒ Hash

Convert an options object to a hash for backward compatibility

Parameters:

  • options (Object)

    an options object created by the options class

Returns:

  • (Hash)

    the options as a hash



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/ukiryu/options_builder.rb', line 69

def to_hash(options)
  hash = {}

  # Get the command definition from the options class
  command_def = options.class.command_def
  return {} unless command_def

  # Extract all argument values
  (command_def.arguments || []).each do |arg_def|
    attr_name = arg_def.name
    value = options.send(attr_name)
    hash[attr_name.to_sym] = value unless value.nil?
  end

  # Extract all option values
  (command_def.options || []).each do |opt_def|
    attr_name = opt_def.name
    value = options.send(attr_name)
    hash[attr_name.to_sym] = value unless value.nil?
  end

  # Extract all flag values
  (command_def.flags || []).each do |flag_def|
    attr_name = flag_def.name
    value = options.send(attr_name)
    # Only include flags that are true
    hash[attr_name.to_sym] = value if value
  end

  # Extract all post_option values
  (command_def.post_options || []).each do |opt_def|
    attr_name = opt_def.name
    value = options.send(attr_name)
    hash[attr_name.to_sym] = value unless value.nil?
  end

  # Include extra_args if present (for manual option injection)
  hash[:extra_args] = options.extra_args if options.respond_to?(:extra_args) && !options.extra_args.nil?

  hash
end