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`.

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

Matches things like ‘’-x123’‘.

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

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

"--"

Constants inherited from Arguments

Arguments::NUMERIC

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Arguments

parse, split

Constructor Details

#initialize(hash_options = {}, defaults = {}, 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.



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

def initialize  hash_options = {},
                defaults = {},
                stop_on_unknown = false,
                disable_required_check = false
  @stop_on_unknown = stop_on_unknown
  @disable_required_check = disable_required_check
  options = hash_options.values
  super(options)

  # Add defaults
  defaults.each do |key, value|
    @assigns[key.to_s] = value
    @non_assigned_required.delete(hash_options[key])
  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



198
199
200
201
202
203
204
# File 'lib/thor/parser/options.rb', line 198

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.



218
219
220
221
222
223
224
225
226
227
# File 'lib/thor/parser/options.rb', line 218

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)



230
231
232
233
234
235
236
237
# File 'lib/thor/parser/options.rb', line 230

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)



210
211
212
# File 'lib/thor/parser/options.rb', line 210

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 ‘-’.



278
279
280
# File 'lib/thor/parser/options.rb', line 278

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

#parse(args) ⇒ Object

rubocop:disable MethodLength



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

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.



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

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.



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

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)



283
284
285
286
# File 'lib/thor/parser/options.rb', line 283

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.



127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/thor/parser/options.rb', line 127

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



101
102
103
# File 'lib/thor/parser/options.rb', line 101

def remaining
  @extra
end

#switch?(arg) ⇒ Boolean (protected)



240
241
242
# File 'lib/thor/parser/options.rb', line 240

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.



258
259
260
261
262
263
264
# File 'lib/thor/parser/options.rb', line 258

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