thor 0.19.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- require "thor/base"
1
+ require_relative "base"
2
2
 
3
3
  # Thor has a special class called Thor::Group. The main difference to Thor class
4
4
  # is that it invokes all commands at once. It also include some methods that allows
@@ -61,7 +61,7 @@ class Thor::Group
61
61
  invocations[name] = false
62
62
  invocation_blocks[name] = block if block_given?
63
63
 
64
- class_eval <<-METHOD, __FILE__, __LINE__
64
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
65
65
  def _invoke_#{name.to_s.gsub(/\W/, '_')}
66
66
  klass, command = self.class.prepare_for_invocation(nil, #{name.inspect})
67
67
 
@@ -120,7 +120,7 @@ class Thor::Group
120
120
  invocations[name] = true
121
121
  invocation_blocks[name] = block if block_given?
122
122
 
123
- class_eval <<-METHOD, __FILE__, __LINE__
123
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
124
124
  def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')}
125
125
  return unless options[#{name.inspect}]
126
126
 
@@ -205,7 +205,7 @@ class Thor::Group
205
205
  alias_method :printable_tasks, :printable_commands
206
206
 
207
207
  def handle_argument_error(command, error, _args, arity) #:nodoc:
208
- msg = "#{basename} #{command.name} takes #{arity} argument"
208
+ msg = "#{basename} #{command.name} takes #{arity} argument".dup
209
209
  msg << "s" if arity > 1
210
210
  msg << ", but it should not."
211
211
  raise error, msg
@@ -1,6 +1,7 @@
1
1
  class Thor
2
2
  module Invocation
3
3
  def self.included(base) #:nodoc:
4
+ super(base)
4
5
  base.extend ClassMethods
5
6
  end
6
7
 
@@ -1,5 +1,5 @@
1
- require "thor/line_editor/basic"
2
- require "thor/line_editor/readline"
1
+ require_relative "line_editor/basic"
2
+ require_relative "line_editor/readline"
3
3
 
4
4
  class Thor
5
5
  module LineEditor
@@ -23,6 +23,8 @@ class Thor
23
23
  if echo?
24
24
  $stdin.gets
25
25
  else
26
+ # Lazy-load io/console since it is gem-ified as of 2.3
27
+ require "io/console"
26
28
  $stdin.noecho(&:gets)
27
29
  end
28
30
  end
@@ -1,19 +1,19 @@
1
- begin
2
- require "readline"
3
- rescue LoadError
4
- end
5
-
6
1
  class Thor
7
2
  module LineEditor
8
3
  class Readline < Basic
9
4
  def self.available?
5
+ begin
6
+ require "readline"
7
+ rescue LoadError
8
+ end
9
+
10
10
  Object.const_defined?(:Readline)
11
11
  end
12
12
 
13
13
  def readline
14
14
  if echo?
15
15
  ::Readline.completion_append_character = nil
16
- # Ruby 1.8.7 does not allow Readline.completion_proc= to receive nil.
16
+ # rb-readline does not allow Readline.completion_proc= to receive nil.
17
17
  if complete = completion_proc
18
18
  ::Readline.completion_proc = complete
19
19
  end
@@ -0,0 +1,29 @@
1
+ class Thor
2
+ class NestedContext
3
+ def initialize
4
+ @depth = 0
5
+ end
6
+
7
+ def enter
8
+ push
9
+
10
+ yield
11
+ ensure
12
+ pop
13
+ end
14
+
15
+ def entered?
16
+ @depth > 0
17
+ end
18
+
19
+ private
20
+
21
+ def push
22
+ @depth += 1
23
+ end
24
+
25
+ def pop
26
+ @depth -= 1
27
+ end
28
+ end
29
+ end
@@ -1,4 +1,4 @@
1
- require "thor/parser/argument"
2
- require "thor/parser/arguments"
3
- require "thor/parser/option"
4
- require "thor/parser/options"
1
+ require_relative "parser/argument"
2
+ require_relative "parser/arguments"
3
+ require_relative "parser/option"
4
+ require_relative "parser/options"
@@ -9,7 +9,7 @@ class Thor
9
9
  arguments = []
10
10
 
11
11
  args.each do |item|
12
- break if item =~ /^-/
12
+ break if item.is_a?(String) && item =~ /^-/
13
13
  arguments << item
14
14
  end
15
15
 
@@ -82,7 +82,7 @@ class Thor
82
82
  end
83
83
 
84
84
  def current_is_value?
85
- peek && peek.to_s !~ /^-/
85
+ peek && peek.to_s !~ /^-{1,2}\S+/
86
86
  end
87
87
 
88
88
  # Runs through the argument array getting strings that contains ":" and
@@ -1,16 +1,18 @@
1
1
  class Thor
2
2
  class Option < Argument #:nodoc:
3
- attr_reader :aliases, :group, :lazy_default, :hide
3
+ attr_reader :aliases, :group, :lazy_default, :hide, :repeatable
4
4
 
5
5
  VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
6
6
 
7
7
  def initialize(name, options = {})
8
+ @check_default_type = options[:check_default_type]
8
9
  options[:required] = false unless options.key?(:required)
10
+ @repeatable = options.fetch(:repeatable, false)
9
11
  super
10
- @lazy_default = options[:lazy_default]
11
- @group = options[:group].to_s.capitalize if options[:group]
12
- @aliases = Array(options[:aliases])
13
- @hide = options[:hide]
12
+ @lazy_default = options[:lazy_default]
13
+ @group = options[:group].to_s.capitalize if options[:group]
14
+ @aliases = Array(options[:aliases])
15
+ @hide = options[:hide]
14
16
  end
15
17
 
16
18
  # This parse quick options given as method_options. It makes several
@@ -80,12 +82,12 @@ class Thor
80
82
 
81
83
  def usage(padding = 0)
82
84
  sample = if banner && !banner.to_s.empty?
83
- "#{switch_name}=#{banner}"
85
+ "#{switch_name}=#{banner}".dup
84
86
  else
85
87
  switch_name
86
88
  end
87
89
 
88
- sample = "[#{sample}]" unless required?
90
+ sample = "[#{sample}]".dup unless required?
89
91
 
90
92
  if boolean?
91
93
  sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-")
@@ -127,8 +129,19 @@ class Thor
127
129
  @default.class.name.downcase.to_sym
128
130
  end
129
131
 
130
- # TODO: This should raise an ArgumentError in a future version of Thor
131
- warn "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type
132
+ expected_type = (@repeatable && @type != :hash) ? :array : @type
133
+
134
+ if default_type != expected_type
135
+ err = "Expected #{expected_type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})"
136
+
137
+ if @check_default_type
138
+ raise ArgumentError, err
139
+ elsif @check_default_type == nil
140
+ Thor.deprecation_warning "#{err}.\n" +
141
+ 'This will be rejected in the future unless you explicitly pass the options `check_default_type: false`' +
142
+ ' or call `allow_incompatible_default_type!` in your code'
143
+ end
144
+ end
132
145
  end
133
146
 
134
147
  def dasherized?
@@ -18,19 +18,20 @@ class Thor
18
18
  when Hash
19
19
  "--#{key} #{value.map { |k, v| "#{k}:#{v}" }.join(' ')}"
20
20
  when nil, false
21
- ""
21
+ nil
22
22
  else
23
23
  "--#{key} #{value.inspect}"
24
24
  end
25
- end.join(" ")
25
+ end.compact.join(" ")
26
26
  end
27
27
 
28
28
  # Takes a hash of Thor::Option and a hash with defaults.
29
29
  #
30
30
  # If +stop_on_unknown+ is true, #parse will stop as soon as it encounters
31
31
  # an unknown option or a regular argument.
32
- def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false)
32
+ def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false, disable_required_check = false)
33
33
  @stop_on_unknown = stop_on_unknown
34
+ @disable_required_check = disable_required_check
34
35
  options = hash_options.values
35
36
  super(options)
36
37
 
@@ -43,6 +44,7 @@ class Thor
43
44
  @shorts = {}
44
45
  @switches = {}
45
46
  @extra = []
47
+ @stopped_parsing_after_extra_index = nil
46
48
 
47
49
  options.each do |option|
48
50
  @switches[option.switch_name] = option
@@ -65,6 +67,7 @@ class Thor
65
67
  if result == OPTS_END
66
68
  shift
67
69
  @parsing_options = false
70
+ @stopped_parsing_after_extra_index ||= @extra.size
68
71
  super
69
72
  else
70
73
  result
@@ -94,10 +97,12 @@ class Thor
94
97
 
95
98
  switch = normalize_switch(switch)
96
99
  option = switch_option(switch)
97
- @assigns[option.human_name] = parse_peek(switch, option)
100
+ result = parse_peek(switch, option)
101
+ assign_result!(option, result)
98
102
  elsif @stop_on_unknown
99
103
  @parsing_options = false
100
104
  @extra << shifted
105
+ @stopped_parsing_after_extra_index ||= @extra.size
101
106
  @extra << shift while peek
102
107
  break
103
108
  elsif match
@@ -111,7 +116,7 @@ class Thor
111
116
  end
112
117
  end
113
118
 
114
- check_requirement!
119
+ check_requirement! unless @disable_required_check
115
120
 
116
121
  assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
117
122
  assigns.freeze
@@ -119,13 +124,24 @@ class Thor
119
124
  end
120
125
 
121
126
  def check_unknown!
127
+ to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra
128
+
122
129
  # an unknown option starts with - or -- and has no more --'s afterward.
123
- unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ }
124
- raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty?
130
+ unknown = to_check.select { |str| str =~ /^--?(?:(?!--).)*$/ }
131
+ raise UnknownArgumentError.new(@switches.keys, unknown) unless unknown.empty?
125
132
  end
126
133
 
127
134
  protected
128
135
 
136
+ def assign_result!(option, result)
137
+ if option.repeatable && option.type == :hash
138
+ (@assigns[option.human_name] ||= {}).merge!(result)
139
+ elsif option.repeatable
140
+ (@assigns[option.human_name] ||= []) << result
141
+ else
142
+ @assigns[option.human_name] = result
143
+ end
144
+ end
129
145
  # Check if the current value in peek is a registered switch.
130
146
  #
131
147
  # Two booleans are returned. The first is true if the current value
@@ -155,7 +171,7 @@ class Thor
155
171
  end
156
172
 
157
173
  def switch?(arg)
158
- switch_option(normalize_switch(arg))
174
+ !switch_option(normalize_switch(arg)).nil?
159
175
  end
160
176
 
161
177
  def switch_option(arg)
@@ -188,7 +204,7 @@ class Thor
188
204
  shift
189
205
  false
190
206
  else
191
- true
207
+ @switches.key?(switch) || !no_or_skip?(switch)
192
208
  end
193
209
  else
194
210
  @switches.key?(switch) || !no_or_skip?(switch)
@@ -25,6 +25,7 @@ class Thor
25
25
  end
26
26
 
27
27
  def self.included(base)
28
+ super(base)
28
29
  # Hack. Make rakefile point to invoker, so rdoc task is generated properly.
29
30
  rakefile = File.basename(caller[0].match(/(.*):\d+/)[1])
30
31
  Rake.application.instance_variable_set(:@rakefile, rakefile)
@@ -1,14 +1,13 @@
1
- require "thor"
2
- require "thor/group"
3
- require "thor/core_ext/io_binary_read"
1
+ require_relative "../thor"
2
+ require_relative "group"
4
3
 
5
- require "fileutils"
6
- require "open-uri"
7
4
  require "yaml"
8
5
  require "digest/md5"
9
6
  require "pathname"
10
7
 
11
8
  class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
9
+ autoload :OpenURI, "open-uri"
10
+
12
11
  map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
13
12
 
14
13
  def self.banner(command, all = false, subcommand = false)
@@ -104,6 +103,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
104
103
  if package == :file
105
104
  File.open(destination, "w") { |f| f.puts contents }
106
105
  else
106
+ require "fileutils"
107
107
  FileUtils.cp_r(name, destination)
108
108
  end
109
109
 
@@ -112,7 +112,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
112
112
 
113
113
  desc "version", "Show Thor version"
114
114
  def version
115
- require "thor/version"
115
+ require_relative "version"
116
116
  say "Thor #{Thor::VERSION}"
117
117
  end
118
118
 
@@ -120,6 +120,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
120
120
  def uninstall(name)
121
121
  raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
122
122
  say "Uninstalling #{name}."
123
+ require "fileutils"
123
124
  FileUtils.rm_rf(File.join(thor_root, (thor_yaml[name][:filename]).to_s))
124
125
 
125
126
  thor_yaml.delete(name)
@@ -138,6 +139,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
138
139
  self.options = options.merge("as" => name)
139
140
 
140
141
  if File.directory? File.expand_path(name)
142
+ require "fileutils"
141
143
  FileUtils.rm_rf(File.join(thor_root, old_filename))
142
144
 
143
145
  thor_yaml.delete(old_filename)
@@ -194,6 +196,7 @@ private
194
196
  yaml_file = File.join(thor_root, "thor.yml")
195
197
 
196
198
  unless File.exist?(yaml_file)
199
+ require "fileutils"
197
200
  FileUtils.mkdir_p(thor_root)
198
201
  yaml_file = File.join(thor_root, "thor.yml")
199
202
  FileUtils.touch(yaml_file)
@@ -24,9 +24,9 @@ class Thor
24
24
  SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
25
25
  attr_writer :shell
26
26
 
27
- autoload :Basic, "thor/shell/basic"
28
- autoload :Color, "thor/shell/color"
29
- autoload :HTML, "thor/shell/html"
27
+ autoload :Basic, File.expand_path("shell/basic", __dir__)
28
+ autoload :Color, File.expand_path("shell/color", __dir__)
29
+ autoload :HTML, File.expand_path("shell/html", __dir__)
30
30
 
31
31
  # Add shell to initialize config values.
32
32
  #
@@ -55,7 +55,7 @@ class Thor
55
55
 
56
56
  # Common methods that are delegated to the shell.
57
57
  SHELL_DELEGATED_METHODS.each do |method|
58
- module_eval <<-METHOD, __FILE__, __LINE__
58
+ module_eval <<-METHOD, __FILE__, __LINE__ + 1
59
59
  def #{method}(*args,&block)
60
60
  shell.#{method}(*args,&block)
61
61
  end
@@ -1,9 +1,8 @@
1
- require "tempfile"
2
- require "io/console" if RUBY_VERSION > "1.9.2"
3
-
4
1
  class Thor
5
2
  module Shell
6
3
  class Basic
4
+ DEFAULT_TERMINAL_WIDTH = 80
5
+
7
6
  attr_accessor :base
8
7
  attr_reader :padding
9
8
 
@@ -48,6 +47,10 @@ class Thor
48
47
 
49
48
  # Asks something to the user and receives a response.
50
49
  #
50
+ # If a default value is specified it will be presented to the user
51
+ # and allows them to select that value with an empty response. This
52
+ # option is ignored when limited answers are supplied.
53
+ #
51
54
  # If asked to limit the correct responses, you can pass in an
52
55
  # array of acceptable answers. If one of those is not supplied,
53
56
  # they will be shown a message stating that one of those answers
@@ -64,6 +67,8 @@ class Thor
64
67
  # ==== Example
65
68
  # ask("What is your name?")
66
69
  #
70
+ # ask("What is the planet furthest from the sun?", :default => "Pluto")
71
+ #
67
72
  # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
68
73
  #
69
74
  # ask("What is your password?", :echo => false)
@@ -110,7 +115,7 @@ class Thor
110
115
  status = set_color status, color, true if color
111
116
 
112
117
  buffer = "#{status}#{spaces}#{message}"
113
- buffer << "\n" unless buffer.end_with?("\n")
118
+ buffer = "#{buffer}\n" unless buffer.end_with?("\n")
114
119
 
115
120
  stdout.print(buffer)
116
121
  stdout.flush
@@ -165,7 +170,7 @@ class Thor
165
170
  colwidth = options[:colwidth]
166
171
  options[:truncate] = terminal_width if options[:truncate] == true
167
172
 
168
- formats << "%-#{colwidth + 2}s" if colwidth
173
+ formats << "%-#{colwidth + 2}s".dup if colwidth
169
174
  start = colwidth ? 1 : 0
170
175
 
171
176
  colcount = array.max { |a, b| a.size <=> b.size }.size
@@ -177,9 +182,9 @@ class Thor
177
182
  maximas << maxima
178
183
  formats << if index == colcount - 1
179
184
  # Don't output 2 trailing spaces when printing the last column
180
- "%-s"
185
+ "%-s".dup
181
186
  else
182
- "%-#{maxima + 2}s"
187
+ "%-#{maxima + 2}s".dup
183
188
  end
184
189
  end
185
190
 
@@ -187,7 +192,7 @@ class Thor
187
192
  formats << "%s"
188
193
 
189
194
  array.each do |row|
190
- sentence = ""
195
+ sentence = "".dup
191
196
 
192
197
  row.each_with_index do |column, index|
193
198
  maxima = maximas[index]
@@ -225,8 +230,20 @@ class Thor
225
230
  paras = message.split("\n\n")
226
231
 
227
232
  paras.map! do |unwrapped|
228
- unwrapped.strip.tr("\n", " ").squeeze(" ").gsub(/.{1,#{width}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") }
229
- end
233
+ counter = 0
234
+ unwrapped.split(" ").inject do |memo, word|
235
+ word = word.gsub(/\n\005/, "\n").gsub(/\005/, "\n")
236
+ counter = 0 if word.include? "\n"
237
+ if (counter + word.length + 1) < width
238
+ memo = "#{memo} #{word}"
239
+ counter += (word.length + 1)
240
+ else
241
+ memo = "#{memo}\n#{word}"
242
+ counter = word.length
243
+ end
244
+ memo
245
+ end
246
+ end.compact!
230
247
 
231
248
  paras.each do |para|
232
249
  para.split("\n").each do |line|
@@ -242,11 +259,11 @@ class Thor
242
259
  #
243
260
  # ==== Parameters
244
261
  # destination<String>:: the destination file to solve conflicts
245
- # block<Proc>:: an optional block that returns the value to be used in diff
262
+ # block<Proc>:: an optional block that returns the value to be used in diff and merge
246
263
  #
247
264
  def file_collision(destination)
248
265
  return true if @always_force
249
- options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
266
+ options = block_given? ? "[Ynaqdhm]" : "[Ynaqh]"
250
267
 
251
268
  loop do
252
269
  answer = ask(
@@ -255,6 +272,9 @@ class Thor
255
272
  )
256
273
 
257
274
  case answer
275
+ when nil
276
+ say ""
277
+ return true
258
278
  when is?(:yes), is?(:force), ""
259
279
  return true
260
280
  when is?(:no), is?(:skip)
@@ -267,6 +287,13 @@ class Thor
267
287
  when is?(:diff)
268
288
  show_diff(destination, yield) if block_given?
269
289
  say "Retrying..."
290
+ when is?(:merge)
291
+ if block_given? && !merge_tool.empty?
292
+ merge(destination, yield)
293
+ return nil
294
+ end
295
+
296
+ say "Please specify merge tool to `THOR_MERGE` env."
270
297
  else
271
298
  say file_collision_help
272
299
  end
@@ -279,11 +306,11 @@ class Thor
279
306
  result = if ENV["THOR_COLUMNS"]
280
307
  ENV["THOR_COLUMNS"].to_i
281
308
  else
282
- unix? ? dynamic_width : 80
309
+ unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH
283
310
  end
284
- result < 10 ? 80 : result
311
+ result < 10 ? DEFAULT_TERMINAL_WIDTH : result
285
312
  rescue
286
- 80
313
+ DEFAULT_TERMINAL_WIDTH
287
314
  end
288
315
 
289
316
  # Called if something goes wrong during the execution. This is used by Thor
@@ -344,12 +371,14 @@ class Thor
344
371
  q - quit, abort
345
372
  d - diff, show the differences between the old and the new
346
373
  h - help, show this help
374
+ m - merge, run merge tool
347
375
  HELP
348
376
  end
349
377
 
350
378
  def show_diff(destination, content) #:nodoc:
351
379
  diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u"
352
380
 
381
+ require "tempfile"
353
382
  Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
354
383
  temp.write content
355
384
  temp.rewind
@@ -411,7 +440,7 @@ class Thor
411
440
 
412
441
  return unless result
413
442
 
414
- result.strip!
443
+ result = result.strip
415
444
 
416
445
  if default && result == ""
417
446
  default
@@ -422,15 +451,41 @@ class Thor
422
451
 
423
452
  def ask_filtered(statement, color, options)
424
453
  answer_set = options[:limited_to]
454
+ case_insensitive = options.fetch(:case_insensitive, false)
425
455
  correct_answer = nil
426
456
  until correct_answer
427
457
  answers = answer_set.join(", ")
428
458
  answer = ask_simply("#{statement} [#{answers}]", color, options)
429
- correct_answer = answer_set.include?(answer) ? answer : nil
459
+ correct_answer = answer_match(answer_set, answer, case_insensitive)
430
460
  say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
431
461
  end
432
462
  correct_answer
433
463
  end
464
+
465
+ def answer_match(possibilities, answer, case_insensitive)
466
+ if case_insensitive
467
+ possibilities.detect{ |possibility| possibility.downcase == answer.downcase }
468
+ else
469
+ possibilities.detect{ |possibility| possibility == answer }
470
+ end
471
+ end
472
+
473
+ def merge(destination, content) #:nodoc:
474
+ require "tempfile"
475
+ Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
476
+ temp.write content
477
+ temp.rewind
478
+ system %(#{merge_tool} "#{temp.path}" "#{destination}")
479
+ end
480
+ end
481
+
482
+ def merge_tool #:nodoc:
483
+ @merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool
484
+ end
485
+
486
+ def git_merge_tool #:nodoc:
487
+ `git config merge.tool`.rstrip rescue ""
488
+ end
434
489
  end
435
490
  end
436
491
  end