thor 0.20.3 → 1.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b35ad01aa15321a80d1e8c579b3c979c3ff6985a98cca96ffc3e21beba4dd7b5
4
- data.tar.gz: f6aa82269a71a418fa99f82ed56672a32da5ce7ebaa4f6f9e2bdb8a16459ec95
3
+ metadata.gz: 2c23d1fdac6ea485b4cbfe91abad33a46e03c00baf2e7e0068c9f8d4ce41e607
4
+ data.tar.gz: 368fe9c87e8a426eaf38278acaba2b7a3171c96c99298ca5e2eddf3b1b466ff6
5
5
  SHA512:
6
- metadata.gz: 3a405c6ac1be920be0d5f1dfe2f4d01ddae7fd68f229d6968c619e243abedfe6adb6b5b58d935b5a8528b4561c6f16fbd9c9ca58befd993ea0bb43950333983e
7
- data.tar.gz: 659d3e822725f0bc9161feb69fc2449dfd4b355279a0c59406abf909f23435f157e97b353deec047cd3dbce38a5214d3cd6010844ffd86f50e797a0ab21eac55
6
+ metadata.gz: 6f2e3b52a657bc5318fed8a5e3f15368d734f62e7b8039a296add626ff6bdc415e6c8a85cea31bdd591a629960854f0d34e47ef81161c63acf4ef140457a7bdb
7
+ data.tar.gz: 8086cd52e16a12fb63dad7df484f0c079feed33d7aa6078d8683f895ddcd92da21be58d26a3b33f89e44d822ae9b01e73ef36672aa77a7befcfa3198523df253
@@ -1,3 +1,19 @@
1
+ # 1.0.1
2
+ * Fix thor when `thor/base` and `thor/group` are required without `thor.rb`.
3
+ * Handle relative source path in `create_link`.
4
+
5
+ # 1.0.0
6
+ * Drop support to Ruby 1.8 and 1.9.
7
+ * Deprecate relying on default `exit_on_failure?`.
8
+ In preparation to make Thor commands exit when there is a failure we are deprecating
9
+ defining a command without defining what behavior is expected when there is a failure.
10
+
11
+ To fix the deprecation you need to define a class method called `exit_on_failure?` returning
12
+
13
+ `false` if you want the current behavior or `true` if you want the new behavior.
14
+ * Deprecate defining an option with the default value using a different type as defined in the option.
15
+ * Allow options to be repeatable. See #674.
16
+
1
17
  # 0.20.3
2
18
  * Support old versions of `did_you_mean`.
3
19
 
@@ -5,7 +21,7 @@
5
21
  * Fix `did_you_mean` support.
6
22
 
7
23
  # 0.20.1
8
- * Support new versions fo ERB.
24
+ * Support new versions of ERB.
9
25
  * Fix `check_unknown_options!` to not check the content that was not parsed, i.e. after a `--` or after the first unknown with `stop_on_unknown_option!`
10
26
  * Add `did_you_mean` support.
11
27
 
@@ -1,5 +1,5 @@
1
1
  require "set"
2
- require "thor/base"
2
+ require_relative "thor/base"
3
3
 
4
4
  class Thor
5
5
  class << self
@@ -90,9 +90,14 @@ class Thor
90
90
  # ==== Parameters
91
91
  # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given command.
92
92
  #
93
- def map(mappings = nil)
93
+ def map(mappings = nil, **kw)
94
94
  @map ||= from_superclass(:map, {})
95
95
 
96
+ if mappings && !kw.empty?
97
+ mappings = kw.merge!(mappings)
98
+ else
99
+ mappings ||= kw
100
+ end
96
101
  if mappings
97
102
  mappings.each do |key, value|
98
103
  if key.respond_to?(:each)
@@ -170,7 +175,7 @@ class Thor
170
175
  handle_no_command_error(meth) unless command
171
176
 
172
177
  shell.say "Usage:"
173
- shell.say " #{banner(command)}"
178
+ shell.say " #{banner(command).split("\n").join("\n ")}"
174
179
  shell.say
175
180
  class_options_help(shell, nil => command.options.values)
176
181
  if command.long_description
@@ -393,7 +398,10 @@ class Thor
393
398
  # the namespace should be displayed as arguments.
394
399
  #
395
400
  def banner(command, namespace = nil, subcommand = false)
396
- "#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
401
+ $thor_runner ||= false
402
+ command.formatted_usage(self, $thor_runner, subcommand).split("\n").map do |formatted_usage|
403
+ "#{basename} #{formatted_usage}"
404
+ end.join("\n")
397
405
  end
398
406
 
399
407
  def baseclass #:nodoc:
@@ -1,17 +1,16 @@
1
- require "uri"
2
- require "thor/core_ext/io_binary_read"
3
- require "thor/actions/create_file"
4
- require "thor/actions/create_link"
5
- require "thor/actions/directory"
6
- require "thor/actions/empty_directory"
7
- require "thor/actions/file_manipulation"
8
- require "thor/actions/inject_into_file"
1
+ require_relative "actions/create_file"
2
+ require_relative "actions/create_link"
3
+ require_relative "actions/directory"
4
+ require_relative "actions/empty_directory"
5
+ require_relative "actions/file_manipulation"
6
+ require_relative "actions/inject_into_file"
9
7
 
10
8
  class Thor
11
9
  module Actions
12
10
  attr_accessor :behavior
13
11
 
14
12
  def self.included(base) #:nodoc:
13
+ super(base)
15
14
  base.extend ClassMethods
16
15
  end
17
16
 
@@ -257,13 +256,19 @@ class Thor
257
256
 
258
257
  return if options[:pretend]
259
258
 
260
- result = config[:capture] ? `#{command}` : system(command.to_s)
259
+ env_splat = [config[:env]] if config[:env]
261
260
 
262
- if config[:abort_on_failure]
263
- success = config[:capture] ? $?.success? : result
264
- abort unless success
261
+ if config[:capture]
262
+ require "open3"
263
+ result, status = Open3.capture2e(*env_splat, command.to_s)
264
+ success = status.success?
265
+ else
266
+ result = system(*env_splat, command.to_s)
267
+ success = result
265
268
  end
266
269
 
270
+ abort if !success && config.fetch(:abort_on_failure, self.class.exit_on_failure?)
271
+
267
272
  result
268
273
  end
269
274
 
@@ -1,4 +1,4 @@
1
- require "thor/actions/empty_directory"
1
+ require_relative "empty_directory"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -1,4 +1,4 @@
1
- require "thor/actions/create_file"
1
+ require_relative "create_file"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -33,7 +33,8 @@ class Thor
33
33
  # Boolean:: true if it is identical, false otherwise.
34
34
  #
35
35
  def identical?
36
- exists? && File.identical?(render, destination)
36
+ source = File.expand_path(render, File.dirname(destination))
37
+ exists? && File.identical?(source, destination)
37
38
  end
38
39
 
39
40
  def invoke!
@@ -1,4 +1,4 @@
1
- require "thor/actions/empty_directory"
1
+ require_relative "empty_directory"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -56,7 +56,7 @@ class Thor
56
56
  attr_reader :source
57
57
 
58
58
  def initialize(base, source, destination = nil, config = {}, &block)
59
- @source = File.expand_path(base.find_in_source_paths(source.to_s))
59
+ @source = File.expand_path(Dir[Util.escape_globs(base.find_in_source_paths(source.to_s))].first)
60
60
  @block = block
61
61
  super(base, destination, {:recursive => true}.merge(config))
62
62
  end
@@ -96,22 +96,12 @@ class Thor
96
96
  end
97
97
  end
98
98
 
99
- if RUBY_VERSION < "2.0"
100
- def file_level_lookup(previous_lookup)
101
- File.join(previous_lookup, "{*,.[a-z]*}")
102
- end
103
-
104
- def files(lookup)
105
- Dir[lookup]
106
- end
107
- else
108
- def file_level_lookup(previous_lookup)
109
- File.join(previous_lookup, "*")
110
- end
99
+ def file_level_lookup(previous_lookup)
100
+ File.join(previous_lookup, "*")
101
+ end
111
102
 
112
- def files(lookup)
113
- Dir.glob(lookup, File::FNM_DOTMATCH)
114
- end
103
+ def files(lookup)
104
+ Dir.glob(lookup, File::FNM_DOTMATCH)
115
105
  end
116
106
  end
117
107
  end
@@ -23,14 +23,14 @@ class Thor
23
23
  destination = args.first || source
24
24
  source = File.expand_path(find_in_source_paths(source.to_s))
25
25
 
26
- create_file destination, nil, config do
26
+ resulting_destination = create_file destination, nil, config do
27
27
  content = File.binread(source)
28
28
  content = yield(content) if block
29
29
  content
30
30
  end
31
31
  if config[:mode] == :preserve
32
32
  mode = File.stat(source).mode
33
- chmod(destination, mode, config)
33
+ chmod(resulting_destination, mode, config)
34
34
  end
35
35
  end
36
36
 
@@ -80,14 +80,14 @@ class Thor
80
80
  config = args.last.is_a?(Hash) ? args.pop : {}
81
81
  destination = args.first
82
82
 
83
- if source =~ %r{^https?\://}
83
+ render = if source =~ %r{^https?\://}
84
84
  require "open-uri"
85
+ URI.send(:open, source) { |input| input.binmode.read }
85
86
  else
86
87
  source = File.expand_path(find_in_source_paths(source.to_s))
88
+ open(source) { |input| input.binmode.read }
87
89
  end
88
90
 
89
- render = open(source) { |input| input.binmode.read }
90
-
91
91
  destination ||= if block_given?
92
92
  block.arity == 1 ? yield(render) : yield
93
93
  else
@@ -1,4 +1,4 @@
1
- require "thor/actions/empty_directory"
1
+ require_relative "empty_directory"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -21,9 +21,14 @@ class Thor
21
21
  # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
22
22
  # end
23
23
  #
24
+ WARNINGS = { unchanged_no_flag: 'File unchanged! The supplied flag value not found!' }
25
+
24
26
  def insert_into_file(destination, *args, &block)
25
27
  data = block_given? ? block : args.shift
26
- config = args.shift
28
+
29
+ config = args.shift || {}
30
+ config[:after] = /\z/ unless config.key?(:before) || config.key?(:after)
31
+
27
32
  action InjectIntoFile.new(self, destination, data, config)
28
33
  end
29
34
  alias_method :inject_into_file, :insert_into_file
@@ -45,8 +50,6 @@ class Thor
45
50
  end
46
51
 
47
52
  def invoke!
48
- say_status :invoke
49
-
50
53
  content = if @behavior == :after
51
54
  '\0' + replacement
52
55
  else
@@ -54,7 +57,11 @@ class Thor
54
57
  end
55
58
 
56
59
  if exists?
57
- replace!(/#{flag}/, content, config[:force])
60
+ if replace!(/#{flag}/, content, config[:force])
61
+ say_status(:invoke)
62
+ else
63
+ say_status(:unchanged, warning: WARNINGS[:unchanged_no_flag], color: :red)
64
+ end
58
65
  else
59
66
  unless pretend?
60
67
  raise Thor::Error, "The file #{ destination } does not appear to exist"
@@ -78,7 +85,7 @@ class Thor
78
85
 
79
86
  protected
80
87
 
81
- def say_status(behavior)
88
+ def say_status(behavior, warning: nil, color: nil)
82
89
  status = if behavior == :invoke
83
90
  if flag == /\A/
84
91
  :prepend
@@ -87,11 +94,13 @@ class Thor
87
94
  else
88
95
  :insert
89
96
  end
97
+ elsif warning
98
+ warning
90
99
  else
91
100
  :subtract
92
101
  end
93
102
 
94
- super(status, config[:verbose])
103
+ super(status, (color || config[:verbose]))
95
104
  end
96
105
 
97
106
  # Adds the content to the file.
@@ -100,8 +109,10 @@ class Thor
100
109
  return if pretend?
101
110
  content = File.read(destination)
102
111
  if force || !content.include?(replacement)
103
- content.gsub!(regexp, string)
112
+ success = content.gsub!(regexp, string)
113
+
104
114
  File.open(destination, "wb") { |file| file.write(content) }
115
+ success
105
116
  end
106
117
  end
107
118
  end
@@ -1,17 +1,17 @@
1
- require "thor/command"
2
- require "thor/core_ext/hash_with_indifferent_access"
3
- require "thor/core_ext/ordered_hash"
4
- require "thor/error"
5
- require "thor/invocation"
6
- require "thor/parser"
7
- require "thor/shell"
8
- require "thor/line_editor"
9
- require "thor/util"
1
+ require_relative "command"
2
+ require_relative "core_ext/hash_with_indifferent_access"
3
+ require_relative "error"
4
+ require_relative "invocation"
5
+ require_relative "nested_context"
6
+ require_relative "parser"
7
+ require_relative "shell"
8
+ require_relative "line_editor"
9
+ require_relative "util"
10
10
 
11
11
  class Thor
12
- autoload :Actions, "thor/actions"
13
- autoload :RakeCompat, "thor/rake_compat"
14
- autoload :Group, "thor/group"
12
+ autoload :Actions, File.expand_path("actions", __dir__)
13
+ autoload :RakeCompat, File.expand_path("rake_compat", __dir__)
14
+ autoload :Group, File.expand_path("group", __dir__)
15
15
 
16
16
  # Shortcuts for help.
17
17
  HELP_MAPPINGS = %w(-h -? --help -D)
@@ -22,6 +22,15 @@ class Thor
22
22
 
23
23
  TEMPLATE_EXTNAME = ".tt"
24
24
 
25
+ class << self
26
+ def deprecation_warning(message) #:nodoc:
27
+ unless ENV['THOR_SILENCE_DEPRECATION']
28
+ warn "Deprecation warning: #{message}\n" +
29
+ 'You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.'
30
+ end
31
+ end
32
+ end
33
+
25
34
  module Base
26
35
  attr_accessor :options, :parent_options, :args
27
36
 
@@ -89,6 +98,7 @@ class Thor
89
98
 
90
99
  class << self
91
100
  def included(base) #:nodoc:
101
+ super(base)
92
102
  base.extend ClassMethods
93
103
  base.send :include, Invocation
94
104
  base.send :include, Shell
@@ -153,17 +163,20 @@ class Thor
153
163
 
154
164
  # If you want to raise an error when the default value of an option does not match
155
165
  # the type call check_default_type!
156
- # This is disabled by default for compatibility.
166
+ # This will be the default; for compatibility a deprecation warning is issued if necessary.
157
167
  def check_default_type!
158
168
  @check_default_type = true
159
169
  end
160
170
 
161
- def check_default_type #:nodoc:
162
- @check_default_type ||= from_superclass(:check_default_type, false)
171
+ # If you want to use defaults that don't match the type of an option,
172
+ # either specify `check_default_type: false` or call `allow_incompatible_default_type!`
173
+ def allow_incompatible_default_type!
174
+ @check_default_type = false
163
175
  end
164
176
 
165
- def check_default_type? #:nodoc:
166
- !!check_default_type
177
+ def check_default_type #:nodoc:
178
+ @check_default_type = from_superclass(:check_default_type, nil) unless defined?(@check_default_type)
179
+ @check_default_type
167
180
  end
168
181
 
169
182
  # If true, option parsing is suspended as soon as an unknown option or a
@@ -353,22 +366,22 @@ class Thor
353
366
  # Returns the commands for this Thor class.
354
367
  #
355
368
  # ==== Returns
356
- # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
357
- # objects as values.
369
+ # Hash:: An ordered hash with commands names as keys and Thor::Command
370
+ # objects as values.
358
371
  #
359
372
  def commands
360
- @commands ||= Thor::CoreExt::OrderedHash.new
373
+ @commands ||= Hash.new
361
374
  end
362
375
  alias_method :tasks, :commands
363
376
 
364
377
  # Returns the commands for this Thor class and all subclasses.
365
378
  #
366
379
  # ==== Returns
367
- # OrderedHash:: An ordered hash with commands names as keys and Thor::Command
368
- # objects as values.
380
+ # Hash:: An ordered hash with commands names as keys and Thor::Command
381
+ # objects as values.
369
382
  #
370
383
  def all_commands
371
- @all_commands ||= from_superclass(:all_commands, Thor::CoreExt::OrderedHash.new)
384
+ @all_commands ||= from_superclass(:all_commands, Hash.new)
372
385
  @all_commands.merge!(commands)
373
386
  end
374
387
  alias_method :all_tasks, :all_commands
@@ -415,14 +428,20 @@ class Thor
415
428
  # remove_command :this_is_not_a_command
416
429
  # end
417
430
  #
418
- def no_commands
419
- @no_commands = true
420
- yield
421
- ensure
422
- @no_commands = false
431
+ def no_commands(&block)
432
+ no_commands_context.enter(&block)
423
433
  end
434
+
424
435
  alias_method :no_tasks, :no_commands
425
436
 
437
+ def no_commands_context
438
+ @no_commands_context ||= NestedContext.new
439
+ end
440
+
441
+ def no_commands?
442
+ no_commands_context.entered?
443
+ end
444
+
426
445
  # Sets the namespace for the Thor or Thor::Group class. By default the
427
446
  # namespace is retrieved from the class name. If your Thor class is named
428
447
  # Scripts::MyScript, the help method, for example, will be called as:
@@ -502,10 +521,16 @@ class Thor
502
521
  msg = "ERROR: \"#{basename} #{name}\" was called with ".dup
503
522
  msg << "no arguments" if args.empty?
504
523
  msg << "arguments " << args.inspect unless args.empty?
505
- msg << "\nUsage: #{banner(command).inspect}"
524
+ msg << "\nUsage: \"#{banner(command).split("\n").join("\"\n \"")}\""
506
525
  raise InvocationError, msg
507
526
  end
508
527
 
528
+ # A flag that makes the process exit with status 1 if any error happens.
529
+ def exit_on_failure?
530
+ Thor.deprecation_warning "Thor exit with status 0 on errors. To keep this behavior, you must define `exit_on_failure?` in `#{self.name}`"
531
+ false
532
+ end
533
+
509
534
  protected
510
535
 
511
536
  # Prints the class options per group. If an option does not belong to
@@ -563,7 +588,7 @@ class Thor
563
588
  # options<Hash>:: Described in both class_option and method_option.
564
589
  # scope<Hash>:: Options hash that is being built up
565
590
  def build_option(name, options, scope) #:nodoc:
566
- scope[name] = Thor::Option.new(name, options.merge(:check_default_type => check_default_type?))
591
+ scope[name] = Thor::Option.new(name, {:check_default_type => check_default_type}.merge!(options))
567
592
  end
568
593
 
569
594
  # Receives a hash of options, parse them and add to the scope. This is a
@@ -596,13 +621,15 @@ class Thor
596
621
  # Everytime someone inherits from a Thor class, register the klass
597
622
  # and file into baseclass.
598
623
  def inherited(klass)
624
+ super(klass)
599
625
  Thor::Base.register_klass_file(klass)
600
- klass.instance_variable_set(:@no_commands, false)
626
+ klass.instance_variable_set(:@no_commands, 0)
601
627
  end
602
628
 
603
629
  # Fire this callback whenever a method is added. Added methods are
604
630
  # tracked as commands by invoking the create_command method.
605
631
  def method_added(meth)
632
+ super(meth)
606
633
  meth = meth.to_s
607
634
 
608
635
  if meth == "initialize"
@@ -613,8 +640,7 @@ class Thor
613
640
  # Return if it's not a public instance method
614
641
  return unless public_method_defined?(meth.to_sym)
615
642
 
616
- @no_commands ||= false
617
- return if @no_commands || !create_command(meth)
643
+ return if no_commands? || !create_command(meth)
618
644
 
619
645
  is_thor_reserved_word?(meth, :command)
620
646
  Thor::Base.register_klass_file(self)
@@ -641,11 +667,6 @@ class Thor
641
667
  end
642
668
  end
643
669
 
644
- # A flag that makes the process exit with status 1 if any error happens.
645
- def exit_on_failure?
646
- false
647
- end
648
-
649
670
  #
650
671
  # The basename of the program invoking the thor class.
651
672
  #
@@ -49,24 +49,32 @@ class Thor
49
49
 
50
50
  formatted ||= "".dup
51
51
 
52
- # Add usage with required arguments
53
- formatted << if klass && !klass.arguments.empty?
54
- usage.to_s.gsub(/^#{name}/) do |match|
55
- match << " " << klass.arguments.map(&:usage).compact.join(" ")
56
- end
57
- else
58
- usage.to_s
59
- end
52
+ Array(usage).map do |specific_usage|
53
+ formatted_specific_usage = formatted
60
54
 
61
- # Add required options
62
- formatted << " #{required_options}"
55
+ formatted_specific_usage += required_arguments_for(klass, specific_usage)
63
56
 
64
- # Strip and go!
65
- formatted.strip
57
+ # Add required options
58
+ formatted_specific_usage += " #{required_options}"
59
+
60
+ # Strip and go!
61
+ formatted_specific_usage.strip
62
+ end.join("\n")
66
63
  end
67
64
 
68
65
  protected
69
66
 
67
+ # Add usage with required arguments
68
+ def required_arguments_for(klass, usage)
69
+ if klass && !klass.arguments.empty?
70
+ usage.to_s.gsub(/^#{name}/) do |match|
71
+ match << " " << klass.arguments.map(&:usage).compact.join(" ")
72
+ end
73
+ else
74
+ usage.to_s
75
+ end
76
+ end
77
+
70
78
  def not_debugging?(instance)
71
79
  !(instance.class.respond_to?(:debugging) && instance.class.debugging)
72
80
  end
@@ -97,8 +105,7 @@ class Thor
97
105
  def handle_argument_error?(instance, error, caller)
98
106
  not_debugging?(instance) && (error.message =~ /wrong number of arguments/ || error.message =~ /given \d*, expected \d*/) && begin
99
107
  saned = sans_backtrace(error.backtrace, caller)
100
- # Ruby 1.9 always include the called method in the backtrace
101
- saned.empty? || (saned.size == 1 && RUBY_VERSION >= "1.9")
108
+ saned.empty? || saned.size == 1
102
109
  end
103
110
  end
104
111
 
@@ -1,22 +1,18 @@
1
1
  class Thor
2
- Correctable =
3
- begin
4
- require 'did_you_mean'
5
-
6
- # In order to support versions of Ruby that don't have keyword
7
- # arguments, we need our own spell checker class that doesn't take key
8
- # words. Even though this code wouldn't be hit because of the check
9
- # above, it's still necessary because the interpreter would otherwise be
10
- # unable to parse the file.
11
- class NoKwargSpellChecker < DidYouMean::SpellChecker # :nodoc:
12
- def initialize(dictionary)
13
- @dictionary = dictionary
14
- end
15
- end
16
-
17
- DidYouMean::Correctable
18
- rescue LoadError, NameError
19
- end
2
+ Correctable = if defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable)
3
+ # In order to support versions of Ruby that don't have keyword
4
+ # arguments, we need our own spell checker class that doesn't take key
5
+ # words. Even though this code wouldn't be hit because of the check
6
+ # above, it's still necessary because the interpreter would otherwise be
7
+ # unable to parse the file.
8
+ class NoKwargSpellChecker < DidYouMean::SpellChecker # :nodoc:
9
+ def initialize(dictionary)
10
+ @dictionary = dictionary
11
+ end
12
+ end
13
+
14
+ DidYouMean::Correctable
15
+ end
20
16
 
21
17
  # Thor::Error is raised when it's caused by wrong usage of thor classes. Those
22
18
  # errors have their backtrace suppressed and are nicely shown to the user.
@@ -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
@@ -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
@@ -24,7 +24,7 @@ class Thor
24
24
  $stdin.gets
25
25
  else
26
26
  # Lazy-load io/console since it is gem-ified as of 2.3
27
- require "io/console" if RUBY_VERSION > "1.9.2"
27
+ require "io/console"
28
28
  $stdin.noecho(&:gets)
29
29
  end
30
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,17 +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
8
  @check_default_type = options[:check_default_type]
9
9
  options[:required] = false unless options.key?(:required)
10
+ @repeatable = options.fetch(:repeatable, false)
10
11
  super
11
- @lazy_default = options[:lazy_default]
12
- @group = options[:group].to_s.capitalize if options[:group]
13
- @aliases = Array(options[:aliases])
14
- @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]
15
16
  end
16
17
 
17
18
  # This parse quick options given as method_options. It makes several
@@ -111,7 +112,7 @@ class Thor
111
112
 
112
113
  def validate!
113
114
  raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
114
- validate_default_type! if @check_default_type
115
+ validate_default_type!
115
116
  end
116
117
 
117
118
  def validate_default_type!
@@ -128,7 +129,19 @@ class Thor
128
129
  @default.class.name.downcase.to_sym
129
130
  end
130
131
 
131
- raise ArgumentError, "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?
@@ -97,7 +97,8 @@ class Thor
97
97
 
98
98
  switch = normalize_switch(switch)
99
99
  option = switch_option(switch)
100
- @assigns[option.human_name] = parse_peek(switch, option)
100
+ result = parse_peek(switch, option)
101
+ assign_result!(option, result)
101
102
  elsif @stop_on_unknown
102
103
  @parsing_options = false
103
104
  @extra << shifted
@@ -132,6 +133,15 @@ class Thor
132
133
 
133
134
  protected
134
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
135
145
  # Check if the current value in peek is a registered switch.
136
146
  #
137
147
  # Two booleans are returned. The first is true if the current value
@@ -161,7 +171,7 @@ class Thor
161
171
  end
162
172
 
163
173
  def switch?(arg)
164
- switch_option(normalize_switch(arg))
174
+ !switch_option(normalize_switch(arg)).nil?
165
175
  end
166
176
 
167
177
  def switch_option(arg)
@@ -194,7 +204,7 @@ class Thor
194
204
  shift
195
205
  false
196
206
  else
197
- !no_or_skip?(switch)
207
+ @switches.key?(switch) || !no_or_skip?(switch)
198
208
  end
199
209
  else
200
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,12 +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
4
  require "yaml"
6
5
  require "digest/md5"
7
6
  require "pathname"
8
7
 
9
8
  class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
9
+ autoload :OpenURI, "open-uri"
10
+
10
11
  map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
11
12
 
12
13
  def self.banner(command, all = false, subcommand = false)
@@ -111,7 +112,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
111
112
 
112
113
  desc "version", "Show Thor version"
113
114
  def version
114
- require "thor/version"
115
+ require_relative "version"
115
116
  say "Thor #{Thor::VERSION}"
116
117
  end
117
118
 
@@ -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
  #
@@ -451,16 +451,25 @@ class Thor
451
451
 
452
452
  def ask_filtered(statement, color, options)
453
453
  answer_set = options[:limited_to]
454
+ case_insensitive = options.fetch(:case_insensitive, false)
454
455
  correct_answer = nil
455
456
  until correct_answer
456
457
  answers = answer_set.join(", ")
457
458
  answer = ask_simply("#{statement} [#{answers}]", color, options)
458
- correct_answer = answer_set.include?(answer) ? answer : nil
459
+ correct_answer = answer_match(answer_set, answer, case_insensitive)
459
460
  say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
460
461
  end
461
462
  correct_answer
462
463
  end
463
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
+
464
473
  def merge(destination, content) #:nodoc:
465
474
  require "tempfile"
466
475
  Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
@@ -1,4 +1,4 @@
1
- require "thor/shell/basic"
1
+ require_relative "basic"
2
2
 
3
3
  class Thor
4
4
  module Shell
@@ -97,7 +97,11 @@ class Thor
97
97
  protected
98
98
 
99
99
  def can_display_colors?
100
- stdout.tty?
100
+ stdout.tty? && !are_colors_disabled?
101
+ end
102
+
103
+ def are_colors_disabled?
104
+ !ENV['NO_COLOR'].nil?
101
105
  end
102
106
 
103
107
  # Overwrite show_diff to show diff with colors if Diff::LCS is
@@ -1,4 +1,4 @@
1
- require "thor/shell/basic"
1
+ require_relative "basic"
2
2
 
3
3
  class Thor
4
4
  module Shell
@@ -51,13 +51,13 @@ class Thor
51
51
  def set_color(string, *colors)
52
52
  if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
53
53
  html_colors = colors.map { |color| lookup_color(color) }
54
- "<span style=\"#{html_colors.join('; ')};\">#{string}</span>"
54
+ "<span style=\"#{html_colors.join('; ')};\">#{Thor::Util.escape_html(string)}</span>"
55
55
  else
56
56
  color, bold = colors
57
57
  html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
58
58
  styles = [html_color]
59
59
  styles << BOLD if bold
60
- "<span style=\"#{styles.join('; ')};\">#{string}</span>"
60
+ "<span style=\"#{styles.join('; ')};\">#{Thor::Util.escape_html(string)}</span>"
61
61
  end
62
62
  end
63
63
 
@@ -263,6 +263,22 @@ class Thor
263
263
  def escape_globs(path)
264
264
  path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
265
265
  end
266
+
267
+ # Returns a string that has had any HTML characters escaped.
268
+ #
269
+ # ==== Examples
270
+ #
271
+ # Thor::Util.escape_html('<div>') # => "&lt;div&gt;"
272
+ #
273
+ # ==== Parameters
274
+ # String
275
+ #
276
+ # ==== Returns
277
+ # String
278
+ #
279
+ def escape_html(string)
280
+ CGI.escapeHTML(string)
281
+ end
266
282
  end
267
283
  end
268
284
  end
@@ -1,3 +1,3 @@
1
1
  class Thor
2
- VERSION = "0.20.3"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
4
4
  require "thor/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.add_development_dependency "bundler", "~> 1.0"
7
+ spec.add_development_dependency "bundler", ">= 1.0", "< 3"
8
8
  spec.authors = ["Yehuda Katz", "José Valim"]
9
9
  spec.description = "Thor is a toolkit for building powerful command-line interfaces."
10
10
  spec.email = "ruby-thor@googlegroups.com"
@@ -13,8 +13,15 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "http://whatisthor.com/"
14
14
  spec.licenses = %w(MIT)
15
15
  spec.name = "thor"
16
+ spec.metadata = {
17
+ "bug_tracker_uri" => "https://github.com/erikhuda/thor/issues",
18
+ "changelog_uri" => "https://github.com/erikhuda/thor/blob/master/CHANGELOG.md",
19
+ "documentation_uri" => "http://whatisthor.com/",
20
+ "source_code_uri" => "https://github.com/erikhuda/thor/tree/v#{Thor::VERSION}",
21
+ "wiki_uri" => "https://github.com/erikhuda/thor/wiki"
22
+ }
16
23
  spec.require_paths = %w(lib)
17
- spec.required_ruby_version = ">= 1.8.7"
24
+ spec.required_ruby_version = ">= 2.0.0"
18
25
  spec.required_rubygems_version = ">= 1.3.5"
19
26
  spec.summary = spec.description
20
27
  spec.version = Thor::VERSION
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.3
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yehuda Katz
@@ -9,22 +9,28 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-11-10 00:00:00.000000000 Z
12
+ date: 2019-12-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "~>"
18
+ - - ">="
19
19
  - !ruby/object:Gem::Version
20
20
  version: '1.0'
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '3'
21
24
  type: :development
22
25
  prerelease: false
23
26
  version_requirements: !ruby/object:Gem::Requirement
24
27
  requirements:
25
- - - "~>"
28
+ - - ">="
26
29
  - !ruby/object:Gem::Version
27
30
  version: '1.0'
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
28
34
  description: Thor is a toolkit for building powerful command-line interfaces.
29
35
  email: ruby-thor@googlegroups.com
30
36
  executables:
@@ -49,14 +55,13 @@ files:
49
55
  - lib/thor/base.rb
50
56
  - lib/thor/command.rb
51
57
  - lib/thor/core_ext/hash_with_indifferent_access.rb
52
- - lib/thor/core_ext/io_binary_read.rb
53
- - lib/thor/core_ext/ordered_hash.rb
54
58
  - lib/thor/error.rb
55
59
  - lib/thor/group.rb
56
60
  - lib/thor/invocation.rb
57
61
  - lib/thor/line_editor.rb
58
62
  - lib/thor/line_editor/basic.rb
59
63
  - lib/thor/line_editor/readline.rb
64
+ - lib/thor/nested_context.rb
60
65
  - lib/thor/parser.rb
61
66
  - lib/thor/parser/argument.rb
62
67
  - lib/thor/parser/arguments.rb
@@ -74,7 +79,12 @@ files:
74
79
  homepage: http://whatisthor.com/
75
80
  licenses:
76
81
  - MIT
77
- metadata: {}
82
+ metadata:
83
+ bug_tracker_uri: https://github.com/erikhuda/thor/issues
84
+ changelog_uri: https://github.com/erikhuda/thor/blob/master/CHANGELOG.md
85
+ documentation_uri: http://whatisthor.com/
86
+ source_code_uri: https://github.com/erikhuda/thor/tree/v1.0.1
87
+ wiki_uri: https://github.com/erikhuda/thor/wiki
78
88
  post_install_message:
79
89
  rdoc_options: []
80
90
  require_paths:
@@ -83,15 +93,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
83
93
  requirements:
84
94
  - - ">="
85
95
  - !ruby/object:Gem::Version
86
- version: 1.8.7
96
+ version: 2.0.0
87
97
  required_rubygems_version: !ruby/object:Gem::Requirement
88
98
  requirements:
89
99
  - - ">="
90
100
  - !ruby/object:Gem::Version
91
101
  version: 1.3.5
92
102
  requirements: []
93
- rubyforge_project:
94
- rubygems_version: 2.7.6
103
+ rubygems_version: 3.0.3
95
104
  signing_key:
96
105
  specification_version: 4
97
106
  summary: Thor is a toolkit for building powerful command-line interfaces.
@@ -1,12 +0,0 @@
1
- class IO #:nodoc:
2
- class << self
3
- unless method_defined? :binread
4
- def binread(file, *args)
5
- raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3
6
- File.open(file, "rb") do |f|
7
- f.read(*args)
8
- end
9
- end
10
- end
11
- end
12
- end
@@ -1,129 +0,0 @@
1
- class Thor
2
- module CoreExt
3
- class OrderedHash < ::Hash
4
- if RUBY_VERSION < "1.9"
5
- def initialize(*args, &block)
6
- super
7
- @keys = []
8
- end
9
-
10
- def initialize_copy(other)
11
- super
12
- # make a deep copy of keys
13
- @keys = other.keys
14
- end
15
-
16
- def []=(key, value)
17
- @keys << key unless key?(key)
18
- super
19
- end
20
-
21
- def delete(key)
22
- if key? key
23
- index = @keys.index(key)
24
- @keys.delete_at index
25
- end
26
- super
27
- end
28
-
29
- def delete_if
30
- super
31
- sync_keys!
32
- self
33
- end
34
-
35
- alias_method :reject!, :delete_if
36
-
37
- def reject(&block)
38
- dup.reject!(&block)
39
- end
40
-
41
- def keys
42
- @keys.dup
43
- end
44
-
45
- def values
46
- @keys.map { |key| self[key] }
47
- end
48
-
49
- def to_hash
50
- self
51
- end
52
-
53
- def to_a
54
- @keys.map { |key| [key, self[key]] }
55
- end
56
-
57
- def each_key
58
- return to_enum(:each_key) unless block_given?
59
- @keys.each { |key| yield(key) }
60
- self
61
- end
62
-
63
- def each_value
64
- return to_enum(:each_value) unless block_given?
65
- @keys.each { |key| yield(self[key]) }
66
- self
67
- end
68
-
69
- def each
70
- return to_enum(:each) unless block_given?
71
- @keys.each { |key| yield([key, self[key]]) }
72
- self
73
- end
74
-
75
- def each_pair
76
- return to_enum(:each_pair) unless block_given?
77
- @keys.each { |key| yield(key, self[key]) }
78
- self
79
- end
80
-
81
- alias_method :select, :find_all
82
-
83
- def clear
84
- super
85
- @keys.clear
86
- self
87
- end
88
-
89
- def shift
90
- k = @keys.first
91
- v = delete(k)
92
- [k, v]
93
- end
94
-
95
- def merge!(other_hash)
96
- if block_given?
97
- other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
98
- else
99
- other_hash.each { |k, v| self[k] = v }
100
- end
101
- self
102
- end
103
-
104
- alias_method :update, :merge!
105
-
106
- def merge(other_hash, &block)
107
- dup.merge!(other_hash, &block)
108
- end
109
-
110
- # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
111
- def replace(other)
112
- super
113
- @keys = other.keys
114
- self
115
- end
116
-
117
- def inspect
118
- "#<#{self.class} #{super}>"
119
- end
120
-
121
- private
122
-
123
- def sync_keys!
124
- @keys.delete_if { |k| !key?(k) }
125
- end
126
- end
127
- end
128
- end
129
- end