Class: Thor::Options

Inherits:
Arguments show all
Defined in:
lib/thor/parser/options.rb

Overview

rubocop:disable ClassLength

Constant Summary collapse

LONG_RE =

Constants

/^(--\w+(?:-\w+)*)$/
SHORT_RE =
/^(-[a-z])$/i
EQ_RE =
/^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
SHORT_SQ_RE =

Matches “multiple short switches”, like ‘-xv`.

Returns:

  • (Regexp)
/^-([a-z]{2,})$/i
SHORT_NUM =

Matches things like ‘’-x123’‘.

Returns:

  • (Regexp)
/^(-[a-z])#{ Thor::Arguments::NUMERIC }$/i
OPTS_END =

The “bare double-dash” used to indicate that following arguments should not be parsed for options.

Returns:

  • (String)
"--"

Constants inherited from Arguments

Arguments::NUMERIC

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Arguments

parse, split

Constructor Details

#initialize(options_to_parse_by_name = {}, option_default_values = {}, stop_on_unknown = false, disable_required_check = false) ⇒ Options

Takes a hash of Thor::Option and a hash with defaults.

If stop_on_unknown is true, #parse will stop as soon as it encounters an unknown option or a regular argument.

Parameters:



68
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
# File 'lib/thor/parser/options.rb', line 68

def initialize  options_to_parse_by_name = {},
                option_default_values = {},
                stop_on_unknown = false,
                disable_required_check = false
  @stop_on_unknown = stop_on_unknown
  @disable_required_check = disable_required_check

  options = options_to_parse_by_name.values
  super( options )

  # Add defaults
  option_default_values.each do |option_name, value|
    @assigns[ option_name.to_s ] = value

    @non_assigned_required.delete \
      options_to_parse_by_name[ option_name ]
  end

  @shorts = {}
  @switches = {}
  @extra = []

  options.each do |option|
    @switches[option.switch_name] = option

    option.aliases.each do |short|
      name = short.to_s.sub(/^(?!\-)/, "-")
      @shorts[name] ||= option.switch_name
    end
  end
end

Class Method Details

.to_switches(options) ⇒ Object

Receives a hash and makes it switches.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/thor/parser/options.rb', line 39

def self.to_switches(options)
  options.map do |key, value|
    case value
    when true
      "--#{key}"
    when Array
      "--#{key} #{value.map(&:inspect).join(' ')}"
    when Hash
      "--#{key} #{value.map { |k, v| "#{k}:#{v}" }.join(' ')}"
    when nil, false
      nil
    else
      "--#{key} #{value.inspect}"
    end
  end.compact.join(" ")
end

Instance Method Details

#check_unknown!Object



201
202
203
204
205
206
207
# File 'lib/thor/parser/options.rb', line 201

def check_unknown!
  # an unknown option starts with - or -- and has no more --'s afterward.
  unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ }
  unless unknown.empty?
    raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'"
  end
end

#current_is_switch?Boolean (protected)

Check if the current value in peek is a registered switch.

Two booleans are returned. The first is true if the current value starts with a hyphen; the second is true if it is a registered switch.

Returns:

  • (Boolean)


221
222
223
224
225
226
227
228
229
230
# File 'lib/thor/parser/options.rb', line 221

def current_is_switch?
  case peek
  when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
    [true, switch?($1)]
  when SHORT_SQ_RE
    [true, $1.split("").any? { |f| switch?("-#{f}") }]
  else
    [false, false]
  end
end

#current_is_switch_formatted?Boolean (protected)

Returns:

  • (Boolean)


233
234
235
236
237
238
239
240
# File 'lib/thor/parser/options.rb', line 233

def current_is_switch_formatted?
  case peek
  when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
    true
  else
    false
  end
end

#last?Boolean (protected)

Returns:

  • (Boolean)


213
214
215
# File 'lib/thor/parser/options.rb', line 213

def last?
  super() || peek == OPTS_END
end

#normalize_switch(raw_switch_arg) ⇒ String (protected)

Check if the given argument is actually a shortcut.

Also normalizes ‘_’ to ‘-’.

Parameters:

  • raw_switch_arg (String)

    The raw switch arg that we received (essentially, what was passed on the CLI).

Returns:

  • (String)

    Normalized, de-aliased switch string.



281
282
283
# File 'lib/thor/parser/options.rb', line 281

def normalize_switch raw_switch_arg
  (@shorts[raw_switch_arg] || raw_switch_arg).tr("_", "-")
end

#parse(args) ⇒ Object

rubocop:disable MethodLength



145
146
147
148
149
150
151
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
# File 'lib/thor/parser/options.rb', line 145

def parse args # rubocop:disable MethodLength
  logger.debug __method__.to_s,
    args: args
  
  @pile = args.dup
  @parsing_options = true

  while peek
    if parsing_options?
      match, is_switch = current_is_switch?
      shifted = shift

      if is_switch
        case shifted
        when SHORT_SQ_RE
          unshift($1.split("").map { |f| "-#{f}" })
          next
        when EQ_RE, SHORT_NUM
          unshift $2
          raw_switch_arg = $1
        when LONG_RE, SHORT_RE
          raw_switch_arg = $1
        end

        switch = normalize_switch raw_switch_arg
        option = switch_option switch
        @assigns[option.human_name] = parse_peek switch, option
      elsif @stop_on_unknown
        @parsing_options = false
        @extra << shifted
        @extra << shift while peek
        break
      elsif match
        @extra << shifted
        @extra << shift while peek && peek !~ /^-/
      else
        @extra << shifted
      end
    else
      @extra << shift
    end
  end

  check_requirement! unless @disable_required_check

  assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
  assigns.freeze
  
  logger.debug "#{ __method__ } done",
    assigns: assigns,
    remaining: remaining
  
  assigns
end

#parse_boolean(switch) ⇒ Object (protected)

Parse boolean values which can be given as –foo=true, –foo or –no-foo.



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/thor/parser/options.rb', line 294

def parse_boolean(switch)
  if current_is_value?
    if ["true", "TRUE", "t", "T", true].include?(peek)
      shift
      true
    elsif ["false", "FALSE", "f", "F", false].include?(peek)
      shift
      false
    else
      !no_or_skip?(switch)
    end
  else
    @switches.key?(switch) || !no_or_skip?(switch)
  end
end

#parse_peek(switch, option) ⇒ Object (protected)

Parse the value at the peek analyzing if it requires an input or not.

Parameters:

  • switch (String)

    The normalized option switch, as returned from #normalize_switch.



316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/thor/parser/options.rb', line 316

def parse_peek switch, option
  if current_is_switch_formatted? || last?
    if option.boolean?
      # No problem for boolean types
    elsif no_or_skip?(switch)
      return nil # User set value to nil
    elsif option.string? && !option.required?
      # Return the default if there is one, else the human name
      return option.lazy_default || option.default || option.human_name
    elsif option.lazy_default
      return option.lazy_default
    else
      raise MalformattedArgumentError,
        "No value provided for option '#{switch}'"
    end
  end

  @non_assigned_required.delete(option)
  send(:"parse_#{option.type}", switch)
end

#parsing_options?Boolean (protected)

Returns:

  • (Boolean)


286
287
288
289
# File 'lib/thor/parser/options.rb', line 286

def parsing_options?
  peek
  @parsing_options
end

#peekObject

Note:

This *used to* remove ‘–` separators (what OPTS_END is), but that was problematic with multiple nested subcommands ’cause Thor classes further down the chain wouldn’t know that it was there and would parse options that had been after it.

Maybe that’s how Thor was supposed to work (???), but it didn’t really jive with me… I’ve always felt like stuff after ‘–` meant **_stop parsing options - these are always args_** since I usually see it used when passing shell commands to other shell commands - which is how I was using it when I came across the issues.

And it ain’t like Thor has any documentation to straiten it out. Hell, this method had no doc when I showed up. The line that dropped the ‘–` has no comment. The Thor::Options class itself had no doc.

So, now it does mean that… ‘–` means “no option parsing after here”. For real.

What’s next?! I think.



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/thor/parser/options.rb', line 130

def peek
  return super unless @parsing_options

  result = super
  if result == OPTS_END
    # Removed, see note above:
    # shift
    @parsing_options = false
    super
  else
    result
  end
end

#remainingObject

Instance Methods



104
105
106
# File 'lib/thor/parser/options.rb', line 104

def remaining
  @extra
end

#switch?(arg) ⇒ Boolean (protected)

Returns:

  • (Boolean)


243
244
245
# File 'lib/thor/parser/options.rb', line 243

def switch?(arg)
  switch_option(normalize_switch(arg))
end

#switch_option(arg) ⇒ Thor::Option? (protected)

Get the option for a switch arg.

Handles parsing ‘–no-<option>` and `–skip-<option>` styles as well.

Parameters:

  • arg (String)

    The switch part of the CLI arg, like ‘–blah`.

Returns:

  • (Thor::Option)

    If we have an option for the switch.

  • (nil)

    If we don’t have an option for the switch.



261
262
263
264
265
266
267
# File 'lib/thor/parser/options.rb', line 261

def switch_option(arg)
  if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition
    @switches[arg] || @switches["--#{match}"]
  else
    @switches[arg]
  end
end