thor 0.20.0 → 0.20.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7fddac6c27d0c13187204ad4a00467eb10abce93
4
- data.tar.gz: 5839be67e757fd66707de47fe1f0c051376e958f
2
+ SHA256:
3
+ metadata.gz: dbcaa89ae8040cc061b9243fce97dbe6345e775ec5916d45d37ed9989bd14d79
4
+ data.tar.gz: b78c5c55482372cb805ac15c7326fe598c0483640b80e089e23ad5a58c574422
5
5
  SHA512:
6
- metadata.gz: 24d622cda205874c6ffa47ae8cb616b8f454f05cb3c56d14f6078e6509c6553111a7669c08776c5914c17f8e261296fd0827f59f778e62d48aa00bca6bacb58d
7
- data.tar.gz: bcb09ac8cfa598b845897068ea707f0b6d7686a327dc7e6caa37c073dc2efc581fd95731cfdf16ca4f1d7b606990984652e9e726e2144df6ad4b68bebd7c8797
6
+ metadata.gz: dfe4f6047443f79cdd7974ad556f39897e97c83dcbd4803a95bc26770e9f0b9f8225e310391863600779ae11c570543d8d97411ede4c77465435c9a24b82d32d
7
+ data.tar.gz: 40ba578626f442737b917a6ffd97fbea548bb512d105f6c35f6e99b91be368e862c1fea1803117659ac6f426b22cf5a4ec26614cdc4c619202144cb03eb58105
@@ -1,3 +1,7 @@
1
+ # 0.20.1
2
+ * Support new versions fo ERB.
3
+ * 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!`
4
+
1
5
  ## 0.20.0
2
6
  * Add `check_default_type!` to check if the default value of an option matches the defined type.
3
7
  It removes the warning on usage and gives the command authors the possibility to check for programming errors.
data/README.md CHANGED
@@ -3,13 +3,11 @@ Thor
3
3
 
4
4
  [![Gem Version](http://img.shields.io/gem/v/thor.svg)][gem]
5
5
  [![Build Status](http://img.shields.io/travis/erikhuda/thor.svg)][travis]
6
- [![Dependency Status](http://img.shields.io/gemnasium/erikhuda/thor.svg)][gemnasium]
7
6
  [![Code Climate](http://img.shields.io/codeclimate/github/erikhuda/thor.svg)][codeclimate]
8
7
  [![Coverage Status](http://img.shields.io/coveralls/erikhuda/thor.svg)][coveralls]
9
8
 
10
9
  [gem]: https://rubygems.org/gems/thor
11
10
  [travis]: http://travis-ci.org/erikhuda/thor
12
- [gemnasium]: https://gemnasium.com/erikhuda/thor
13
11
  [codeclimate]: https://codeclimate.com/github/erikhuda/thor
14
12
  [coveralls]: https://coveralls.io/r/erikhuda/thor
15
13
 
@@ -21,7 +19,13 @@ utilities. It removes the pain of parsing command line options, writing
21
19
  build tool. The syntax is Rake-like, so it should be familiar to most Rake
22
20
  users.
23
21
 
22
+ Please note: Thor, by design, is a system tool created to allow seamless file and url
23
+ access, which should not receive application user input. It relies on [open-uri][open-uri],
24
+ which combined with application user input would provide a command injection attack
25
+ vector.
26
+
24
27
  [rake]: https://github.com/ruby/rake
28
+ [open-uri]: https://ruby-doc.org/stdlib-2.5.1/libdoc/open-uri/rdoc/index.html
25
29
 
26
30
  Installation
27
31
  ------------
@@ -113,8 +113,10 @@ class Thor
113
113
  # the script started).
114
114
  #
115
115
  def relative_to_original_destination_root(path, remove_dot = true)
116
- path = path.dup
117
- if path.gsub!(@destination_stack[0], ".")
116
+ root = @destination_stack[0]
117
+ if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ''].include?(path[root.size..root.size])
118
+ path = path.dup
119
+ path[0...root.size] = '.'
118
120
  remove_dot ? (path[2..-1] || "") : path
119
121
  else
120
122
  path
@@ -217,6 +219,7 @@ class Thor
217
219
  shell.padding += 1 if verbose
218
220
 
219
221
  contents = if is_uri
222
+ require "open-uri"
220
223
  open(path, "Accept" => "application/x-thor-template", &:read)
221
224
  else
222
225
  open(path, &:read)
@@ -252,9 +255,16 @@ class Thor
252
255
 
253
256
  say_status :run, desc, config.fetch(:verbose, true)
254
257
 
255
- unless options[:pretend]
256
- config[:capture] ? `#{command}` : system(command.to_s)
258
+ return if options[:pretend]
259
+
260
+ result = config[:capture] ? `#{command}` : system(command.to_s)
261
+
262
+ if config[:abort_on_failure]
263
+ success = config[:capture] ? $?.success? : result
264
+ abort unless success
257
265
  end
266
+
267
+ result
258
268
  end
259
269
 
260
270
  # Executes a ruby script (taking into account WIN32 platform quirks).
@@ -60,6 +60,9 @@ class Thor
60
60
  # destination. If a block is given instead of destination, the content of
61
61
  # the url is yielded and used as location.
62
62
  #
63
+ # +get+ relies on open-uri, so passing application user input would provide
64
+ # a command injection attack vector.
65
+ #
63
66
  # ==== Parameters
64
67
  # source<String>:: the address of the given content.
65
68
  # destination<String>:: the relative path to the destination root.
@@ -117,7 +120,13 @@ class Thor
117
120
  context = config.delete(:context) || instance_eval("binding")
118
121
 
119
122
  create_file destination, nil, config do
120
- content = CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer").tap do |erb|
123
+ match = ERB.version.match(/(\d+\.\d+\.\d+)/)
124
+ capturable_erb = if match && match[1] >= "2.2.0" # Ruby 2.6+
125
+ CapturableERB.new(::File.binread(source), :trim_mode => "-", :eoutvar => "@output_buffer")
126
+ else
127
+ CapturableERB.new(::File.binread(source), nil, "-", "@output_buffer")
128
+ end
129
+ content = capturable_erb.tap do |erb|
121
130
  erb.filename = source
122
131
  end.result(context)
123
132
  content = yield(content) if block
@@ -301,7 +310,7 @@ class Thor
301
310
  def comment_lines(path, flag, *args)
302
311
  flag = flag.respond_to?(:source) ? flag.source : flag
303
312
 
304
- gsub_file(path, /^(\s*)([^#|\n]*#{flag})/, '\1# \2', *args)
313
+ gsub_file(path, /^(\s*)([^#\n]*#{flag})/, '\1# \2', *args)
305
314
  end
306
315
 
307
316
  # Removes a file at the given location.
@@ -113,7 +113,7 @@ class Thor
113
113
  end
114
114
 
115
115
  # Whenever a class inherits from Thor or Thor::Group, we should track the
116
- # class and the file on Thor::Base. This is the method responsable for it.
116
+ # class and the file on Thor::Base. This is the method responsible for it.
117
117
  #
118
118
  def register_klass_file(klass) #:nodoc:
119
119
  file = caller[1].match(/(.*):\d+/)[1]
@@ -466,13 +466,13 @@ class Thor
466
466
  dispatch(nil, given_args.dup, nil, config)
467
467
  rescue Thor::Error => e
468
468
  config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
469
- exit(1) if exit_on_failure?
469
+ exit(false) if exit_on_failure?
470
470
  rescue Errno::EPIPE
471
471
  # This happens if a thor command is piped to something like `head`,
472
472
  # which closes the pipe when it's done reading. This will also
473
473
  # mean that if the pipe is closed, further unnecessary
474
474
  # computation will not occur.
475
- exit(0)
475
+ exit(true)
476
476
  end
477
477
 
478
478
  # Allows to use private methods from parent in child classes as commands.
@@ -493,8 +493,7 @@ class Thor
493
493
  alias_method :public_task, :public_command
494
494
 
495
495
  def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
496
- raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." if has_namespace
497
- raise UndefinedCommandError, "Could not find command #{command.inspect}."
496
+ raise UndefinedCommandError.new(command, all_commands.keys, (namespace if has_namespace))
498
497
  end
499
498
  alias_method :handle_no_task_error, :handle_no_command_error
500
499
 
@@ -1,4 +1,25 @@
1
1
  class Thor
2
+ Correctable =
3
+ begin
4
+ require 'did_you_mean'
5
+
6
+ module DidYouMean
7
+ # In order to support versions of Ruby that don't have keyword
8
+ # arguments, we need our own spell checker class that doesn't take key
9
+ # words. Even though this code wouldn't be hit because of the check
10
+ # above, it's still necessary because the interpreter would otherwise be
11
+ # unable to parse the file.
12
+ class NoKwargSpellChecker < SpellChecker
13
+ def initialize(dictionary)
14
+ @dictionary = dictionary
15
+ end
16
+ end
17
+ end
18
+
19
+ DidYouMean::Correctable
20
+ rescue LoadError
21
+ end
22
+
2
23
  # Thor::Error is raised when it's caused by wrong usage of thor classes. Those
3
24
  # errors have their backtrace suppressed and are nicely shown to the user.
4
25
  #
@@ -10,6 +31,35 @@ class Thor
10
31
 
11
32
  # Raised when a command was not found.
12
33
  class UndefinedCommandError < Error
34
+ class SpellChecker
35
+ attr_reader :error
36
+
37
+ def initialize(error)
38
+ @error = error
39
+ end
40
+
41
+ def corrections
42
+ @corrections ||= spell_checker.correct(error.command).map(&:inspect)
43
+ end
44
+
45
+ def spell_checker
46
+ DidYouMean::NoKwargSpellChecker.new(error.all_commands)
47
+ end
48
+ end
49
+
50
+ attr_reader :command, :all_commands
51
+
52
+ def initialize(command, all_commands, namespace)
53
+ @command = command
54
+ @all_commands = all_commands
55
+
56
+ message = "Could not find command #{command.inspect}"
57
+ message = namespace ? "#{message} in #{namespace.inspect} namespace." : "#{message}."
58
+
59
+ super(message)
60
+ end
61
+
62
+ prepend Correctable if Correctable
13
63
  end
14
64
  UndefinedTaskError = UndefinedCommandError
15
65
 
@@ -22,6 +72,34 @@ class Thor
22
72
  end
23
73
 
24
74
  class UnknownArgumentError < Error
75
+ class SpellChecker
76
+ attr_reader :error
77
+
78
+ def initialize(error)
79
+ @error = error
80
+ end
81
+
82
+ def corrections
83
+ @corrections ||=
84
+ error.unknown.flat_map { |unknown| spell_checker.correct(unknown) }.uniq.map(&:inspect)
85
+ end
86
+
87
+ def spell_checker
88
+ @spell_checker ||=
89
+ DidYouMean::NoKwargSpellChecker.new(error.switches)
90
+ end
91
+ end
92
+
93
+ attr_reader :switches, :unknown
94
+
95
+ def initialize(switches, unknown)
96
+ @switches = switches
97
+ @unknown = unknown
98
+
99
+ super("Unknown switches #{unknown.map(&:inspect).join(', ')}")
100
+ end
101
+
102
+ prepend Correctable if Correctable
25
103
  end
26
104
 
27
105
  class RequiredArgumentMissingError < InvocationError
@@ -29,4 +107,11 @@ class Thor
29
107
 
30
108
  class MalformattedArgumentError < InvocationError
31
109
  end
110
+
111
+ if Correctable
112
+ DidYouMean::SPELL_CHECKERS.merge!(
113
+ 'Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker,
114
+ 'Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker
115
+ )
116
+ end
32
117
  end
@@ -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
 
@@ -44,6 +44,7 @@ class Thor
44
44
  @shorts = {}
45
45
  @switches = {}
46
46
  @extra = []
47
+ @stopped_parsing_after_extra_index = nil
47
48
 
48
49
  options.each do |option|
49
50
  @switches[option.switch_name] = option
@@ -66,6 +67,7 @@ class Thor
66
67
  if result == OPTS_END
67
68
  shift
68
69
  @parsing_options = false
70
+ @stopped_parsing_after_extra_index ||= @extra.size
69
71
  super
70
72
  else
71
73
  result
@@ -99,6 +101,7 @@ class Thor
99
101
  elsif @stop_on_unknown
100
102
  @parsing_options = false
101
103
  @extra << shifted
104
+ @stopped_parsing_after_extra_index ||= @extra.size
102
105
  @extra << shift while peek
103
106
  break
104
107
  elsif match
@@ -120,9 +123,11 @@ class Thor
120
123
  end
121
124
 
122
125
  def check_unknown!
126
+ to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra
127
+
123
128
  # an unknown option starts with - or -- and has no more --'s afterward.
124
- unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ }
125
- raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty?
129
+ unknown = to_check.select { |str| str =~ /^--?(?:(?!--).)*$/ }
130
+ raise UnknownArgumentError.new(@switches.keys, unknown) unless unknown.empty?
126
131
  end
127
132
 
128
133
  protected
@@ -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,6 +1,8 @@
1
1
  class Thor
2
2
  module Shell
3
3
  class Basic
4
+ DEFAULT_TERMINAL_WIDTH = 80
5
+
4
6
  attr_accessor :base
5
7
  attr_reader :padding
6
8
 
@@ -45,6 +47,10 @@ class Thor
45
47
 
46
48
  # Asks something to the user and receives a response.
47
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
+ #
48
54
  # If asked to limit the correct responses, you can pass in an
49
55
  # array of acceptable answers. If one of those is not supplied,
50
56
  # they will be shown a message stating that one of those answers
@@ -61,6 +67,8 @@ class Thor
61
67
  # ==== Example
62
68
  # ask("What is your name?")
63
69
  #
70
+ # ask("What is the planet furthest from the sun?", :default => "Pluto")
71
+ #
64
72
  # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
65
73
  #
66
74
  # ask("What is your password?", :echo => false)
@@ -222,8 +230,20 @@ class Thor
222
230
  paras = message.split("\n\n")
223
231
 
224
232
  paras.map! do |unwrapped|
225
- unwrapped.strip.tr("\n", " ").squeeze(" ").gsub(/.{1,#{width}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") }
226
- 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!
227
247
 
228
248
  paras.each do |para|
229
249
  para.split("\n").each do |line|
@@ -239,11 +259,11 @@ class Thor
239
259
  #
240
260
  # ==== Parameters
241
261
  # destination<String>:: the destination file to solve conflicts
242
- # 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
243
263
  #
244
264
  def file_collision(destination)
245
265
  return true if @always_force
246
- options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
266
+ options = block_given? ? "[Ynaqdhm]" : "[Ynaqh]"
247
267
 
248
268
  loop do
249
269
  answer = ask(
@@ -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,6 +371,7 @@ 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
 
@@ -432,6 +460,23 @@ class Thor
432
460
  end
433
461
  correct_answer
434
462
  end
463
+
464
+ def merge(destination, content) #:nodoc:
465
+ require "tempfile"
466
+ Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
467
+ temp.write content
468
+ temp.rewind
469
+ system %(#{merge_tool} "#{temp.path}" "#{destination}")
470
+ end
471
+ end
472
+
473
+ def merge_tool #:nodoc:
474
+ @merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool
475
+ end
476
+
477
+ def git_merge_tool #:nodoc:
478
+ `git config merge.tool`.rstrip rescue ""
479
+ end
435
480
  end
436
481
  end
437
482
  end
@@ -27,7 +27,7 @@ class Thor
27
27
  end
28
28
 
29
29
  # Receives a constant and converts it to a Thor namespace. Since Thor
30
- # commands can be added to a sandbox, this method is also responsable for
30
+ # commands can be added to a sandbox, this method is also responsible for
31
31
  # removing the sandbox namespace.
32
32
  #
33
33
  # This method should not be used in general because it's used to deal with
@@ -1,3 +1,3 @@
1
1
  class Thor
2
- VERSION = "0.20.0"
2
+ VERSION = "0.20.1"
3
3
  end
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.0
4
+ version: 0.20.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yehuda Katz
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-08-16 00:00:00.000000000 Z
12
+ date: 2018-11-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -91,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
91
  version: 1.3.5
92
92
  requirements: []
93
93
  rubyforge_project:
94
- rubygems_version: 2.6.12
94
+ rubygems_version: 2.7.6
95
95
  signing_key:
96
96
  specification_version: 4
97
97
  summary: Thor is a toolkit for building powerful command-line interfaces.