Module: Rubycom::ParameterExtract

Defined in:
lib/rubycom/parameter_extract.rb

Class Method Summary collapse

Class Method Details

.check(command, parsed_command_line, command_doc) ⇒ Array

Provides upfront checking for this inputs to #extract_parameters and raises a ParameterExtractError if parsed_command_line includes a help argument, option, or flag

Parameters:

  • command (Method)

    the method whose parameters should be resolved

  • parsed_command_line (Hash)

    :args => array of arguments, :opts => { opt_key => opt_val }, :flags => { flag_key => flag_val }

  • command_doc (Hash)

    :parameters => an array consisting of a hash for method parameter where :param_name => the param name as a string, :type => :req|:opt|:rest, :default => the default value for the param

Returns:

  • (Array)

    the given parameters if none of the checks raised an error

Raises:



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/rubycom/parameter_extract.rb', line 28

def self.check(command, parsed_command_line, command_doc)
  has_help_optional = false
  command.parameters.select { |type, _| type == :opt }.map { |_, name| name.to_s }.each { |param|
    has_help_optional = ['help', 'h'].include?(param)
  } if command.class == Method
  help_opt = !parsed_command_line[:opts].nil? && [
      parsed_command_line[:opts]['help'],
      parsed_command_line[:opts]['h']
  ].include?(true)
  help_flag = !parsed_command_line[:flags].nil? && [
      parsed_command_line[:flags]['help'],
      parsed_command_line[:flags]['h']
  ].include?(true)
  if !has_help_optional && (help_opt || help_flag)
    raise ParameterExtractError, 'Help Requested'
  end

  raise ParameterExtractError, "No command specified." if command.nil?
  raise ParameterExtractError, "No command specified." if command.class == Module
  raise ParameterExtractError, "Unrecognized command." unless [Method, Module].include?(command.class)
  raise "#{parsed_command_line} should be a Hash but was #{parsed_command_line.class}" if parsed_command_line.class != Hash

  raise ArgumentError, "command_doc should be a Hash but was #{command_doc.class}" unless command_doc.class == Hash
  raise ArgumentError, "command_doc should have key :parameters" unless command_doc.has_key?(:parameters)
  raise ArgumentError, "command_doc[:parameters] should be an array but was #{command_doc[:parameters].class}" unless command_doc[:parameters].class == Array
  command_doc[:parameters].each { |param_hsh|
    raise ArgumentError, "parameter #{param_hsh} should be a Hash but was #{param_hsh.class}" unless param_hsh.class == Hash
    raise ArgumentError, "parameter #{param_hsh} should have key :param_name" unless param_hsh.has_key?(:param_name)
    raise ArgumentError, "parameter #{param_hsh} should have key :type" unless param_hsh.has_key?(:type)
    raise ArgumentError, "parameter #{param_hsh} should have key :default" unless param_hsh.has_key?(:default)
  }
  [command, parsed_command_line, command_doc]
end

.extract!(long_name, short_name, opts, flags, args) ⇒ Hash

Searches opts, then flags, then args for a key matching the given long_name or short_name The first matched key will be removed from the set. The valued paired to the matched key will be returned along with a hash containing the remaining args, opts, and flags. !destructively modifies opts, flags, and args by deleting or shifting a matched value out of the Hash/Array

value is left is args then the first value in args will be pulled as the value to return

Parameters:

  • long_name (Object)

    the first key to be searched for in each opts, flags, args

  • short_name (Object)

    the key to be searched for if the long_key is not found in the group under search

  • opts (Hash)

    long_key|short_key => option value for that key

  • flags (Hash)

    long_key|short_key => true|false value for that key

  • args (Array)

    if no matching keys for the long or short name exist in either opts or flags and at least one

Returns:

  • (Hash)

    the extracted value or :rubycom_no_value if a value could not be matched and args was empty



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/rubycom/parameter_extract.rb', line 241

def self.extract!(long_name, short_name, opts, flags, args)
  opts = {} if opts.nil?
  flags = {} if flags.nil?
  args = [] if args.nil?

  if opts.has_key?(long_name)
    opts.delete(long_name)
  elsif opts.has_key?(short_name)
    opts.delete(short_name)
  elsif flags.has_key?(long_name)
    flags.delete(long_name)
  elsif flags.has_key?(short_name)
    flags.delete(short_name)
  elsif args.size > 0
    args.shift
  else
    :rubycom_no_value
  end
end

.extract_command_args!(command_name, command_line) ⇒ Hash

Trims command_line down to the entries which occur after command_name

Parameters:

  • command_name (Object)

    the entry in command_line which marks the start of the args to be returned

  • command_line (Hash)

    :args => array of arguments

Returns:

  • (Hash)

    :args => array of arguments including only the entries which occur after the command_name

Raises:

  • (ArgumentError)


108
109
110
111
112
113
114
115
116
117
# File 'lib/rubycom/parameter_extract.rb', line 108

def self.extract_command_args!(command_name, command_line)
  raise ArgumentError, "command_name should be a String|Symbol but was #{command_name}" unless [String, Symbol].include?(command_name.class)
  raise ArgumentError, "command_line should be a hash but was #{command_line}" unless command_line.class == Hash
  return command_line if command_line[:args].nil?

  i = command_line[:args].index(command_name.to_s)
  command_line[:args] = (i.nil?) ? [] : command_line[:args][i..-1]
  command_line[:args].shift if command_line[:args].first == command_name.to_s
  command_line
end

.extract_parameters(command, parsed_command_line, command_doc) ⇒ Hash

Calls #resolve_params with the given parameters after calling #check to assert the state of the inputs

Parameters:

  • command (Method)

    the method whose parameters should be resolved

  • parsed_command_line (Hash)

    :args => array of arguments, :opts => { opt_key => opt_val }, :flags => { flag_key => flag_val }

  • command_doc (Hash)

    :parameters => an array consisting of a hash for method parameter where :param_name => the param name as a string, :type => :req|:opt|:rest, :default => the default value for the param

Returns:

  • (Hash)

    command.parameters.each => the value for that parameter extracted from parsed_command_line or the default in command_doc



13
14
15
16
# File 'lib/rubycom/parameter_extract.rb', line 13

def self.extract_parameters(command, parsed_command_line, command_doc)
  command, parsed_command_line, command_doc = self.check(command, parsed_command_line, command_doc)
  self.resolve_params(command, parsed_command_line, command_doc)
end

.get_param_names(params) ⇒ Hash

Creates a long and short name for each symbol in the given params

short => the first char in symbol if unique in params or the string form of symbol if not

Parameters:

  • params (Array)

    a list of Symbols to create names for

Returns:

  • (Hash)

    params.each symbol => a Hash where long => string form of symbol and



140
141
142
143
144
145
146
147
148
149
150
# File 'lib/rubycom/parameter_extract.rb', line 140

def self.get_param_names(params)
  first_char_map = params.group_by { |_, sym| sym.to_s[0] }
  params.map { |_, sym|
    {
        sym => {
            long: sym.to_s,
            short: (first_char_map[sym.to_s[0]].size == 1) ? sym.to_s[0] : sym.to_s
        }
    }
  }.reduce({}, &:merge)
end

.join(left, right) ⇒ Hash

Calls #update on left passing in right and resolving conflicts by combining left and right values in an array

Parameters:

  • left (Hash)

    the base thing to be updated

  • right (Hash)

    the thing whose keys will be added to left and values combined with left on key collisions

Returns:

  • (Hash)

    left.keys + right.keys where each key => values from left or right or combined in an array if both



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/rubycom/parameter_extract.rb', line 192

def self.join(left, right)
  left.update(right) { |_, left_val, right_val|
    if left_val.class == Array
      combined = left_val
    else
      combined = [left_val]
    end

    if right_val.class == Array
      right_val.each { |rv|
        combined << rv
      }
    else
      combined << right_val
    end

    combined
  }
end

.resolve_opt!(param_name, long_name, short_name, default_value, command_line) ⇒ Hash

Extracts the a value from command_line for the param_name or returns the default with command_line has no values

Parameters:

  • param_name (Object)

    the key in the returned hash

  • default_value (Object)

    the value in the returned hash if no value could be extracted from command_line

  • command_line (Hash)

    :args => array of arguments, :opts => { opt_key => opt_val }, :flags => { flag_key => flag_val }

Returns:

  • (Hash)

    param_name => extracted_value|default_value

Raises:

  • (ArgumentError)


125
126
127
128
129
130
131
132
133
# File 'lib/rubycom/parameter_extract.rb', line 125

def self.resolve_opt!(param_name, long_name, short_name, default_value, command_line)
  raise ArgumentError, "command_line should be a hash but was #{command_line}" unless command_line.class == Hash
  extraction = self.extract!(long_name, short_name, command_line[:opts], command_line[:flags], command_line[:args])
  if extraction == :rubycom_no_value
    {param_name => default_value}
  else
    {param_name => extraction}
  end
end

.resolve_others!(param_name, type, default_value, command_line) ⇒ Hash

Extracts a value from the command_line or returns the default_value if the command_line has no values. Raises a ParameterExtractError if the type was :req and no value was found.

Parameters:

  • param_name (Object)

    the key in the returned hash

  • type (Symbol)

    :req if the parameter is required

  • default_value (Object)

    the value in the returned hash if no value could be extracted from command_line

  • command_line (Hash)

    :args => array of arguments

Returns:

  • (Hash)

    param_name => value extracted for param_name



220
221
222
223
224
225
226
227
# File 'lib/rubycom/parameter_extract.rb', line 220

def self.resolve_others!(param_name, type, default_value, command_line)
  if command_line[:args].size > 0
    {param_name => (command_line[:args].shift)}
  else
    raise ParameterExtractError, "Missing required argument: #{param_name}" if type == :req
    {param_name => default_value}
  end
end

.resolve_params(command, command_line, command_doc) ⇒ Hash

Matches parameter names in command.parameters to values from command_line or their default values in command_doc

Parameters:

  • command (Method)

    the method whose parameters should be resolved

  • command_line (Hash)

    :args => array of arguments, :opts => { opt_key => opt_val }, :flags => { flag_key => flag_val }

  • command_doc (Hash)

    :parameters => an array consisting of a hash for method parameter where :param_name => the param name as a string, :type => :req|:opt|:rest, :default => the default value for the param

Returns:

  • (Hash)

    command.parameters.each => the value for that parameter extracted from command_line or the default in command_doc

Raises:

  • (ArgumentError)


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
# File 'lib/rubycom/parameter_extract.rb', line 71

def self.resolve_params(command, command_line, command_doc)
  raise ArgumentError, "command should be a Method but was #{command.class}" unless command.class == Method
  command_line = command_line.clone.map { |type, entry|
    {type => entry.clone}
  }.reduce({}, &:merge)
  command_line = self.extract_command_args!(command.name.to_s, command_line)
  params = command.parameters
  param_names = self.get_param_names(params)
  raise ArgumentError, "command_doc should have key :parameters but was #{command_doc}" unless command_doc.has_key?(:parameters)
  param_docs = command_doc[:parameters].map { |param_hsh|
    if param_hsh[:type] == :rest
      {param_hsh.fetch(:param_name).reverse.chomp('*').reverse.to_sym => param_hsh.reject { |k, _| k == :param_name }}
    else
      {param_hsh.fetch(:param_name).to_sym => param_hsh.reject { |k, _| k == :param_name }}
    end
  }.reduce({}, &:merge)

  params.map { |type, sym|
    case type
      when :opt
        unless param_docs.has_key?(sym) && param_docs[sym].has_key?(:default)
          raise ArgumentError, "#{sym} should exist in command_doc[:parameters] and have key :default but has values #{param_docs[sym]}"
        end
        self.resolve_opt!(sym, param_names[sym][:long], param_names[sym][:short], param_docs[sym][:default], command_line)
      when :rest
        self.resolve_rest!(sym, param_docs[sym][:default], command_line)
      else
        self.resolve_others!(sym, type, param_docs[sym][:default], command_line)
    end
  }.reduce({}, &:merge).reject { |_, val| val == :rubycom_no_value }
end

.resolve_rest!(param_name, default_value, command_line) ⇒ Array

Extracts the remaining values from command_line as an Array or returns the default with command_line has no values

Parameters:

  • param_name (Object)

    the key in the returned hash

  • default_value (Object)

    the value in the returned hash if no value could be extracted from command_line

  • command_line (Hash)

    :args => array of arguments, :opts => { opt_key => opt_val }, :flags => { flag_key => flag_val }

Returns:

  • (Array)

    the rest of the keys/values in command_line



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
# File 'lib/rubycom/parameter_extract.rb', line 158

def self.resolve_rest!(param_name, default_value, command_line)
  args = command_line[:args] || []
  opts = command_line[:opts] || {}
  flags = command_line[:flags] || {}
  # TODO seems like we still can not call out a rest param on the command line, not sure if that is a problem
  rest_arr = {
      param_name => if args.empty? && opts.empty? && flags.empty?
                      default_value
                    elsif !args.empty? && opts.empty? && flags.empty?
                      args
                    elsif args.empty? && (!opts.empty? || !flags.empty?)
                      joined = self.join(flags, opts)
                      keyed = joined[param_name] || []
                      keyed.to_a << joined.reject { |k, _| k == param_name }
                    else
                      joined = self.join(flags, opts)
                      keyed = joined[param_name] || []
                      rest = keyed.to_a << joined.reject { |k, _| k == param_name }
                      args + rest
                    end
  }

  command_line[:opts] = {} unless command_line[:opts].nil?
  command_line[:flags] = {} unless command_line[:flags].nil?
  command_line[:args] = [] unless command_line[:args].nil?

  rest_arr
end