thor 0.19.4 → 0.20.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +41 -0
- data/README.md +6 -2
- data/lib/thor.rb +25 -8
- data/lib/thor/actions.rb +18 -5
- data/lib/thor/actions/create_file.rb +1 -0
- data/lib/thor/actions/create_link.rb +1 -0
- data/lib/thor/actions/empty_directory.rb +9 -1
- data/lib/thor/actions/file_manipulation.rb +55 -9
- data/lib/thor/actions/inject_into_file.rb +9 -3
- data/lib/thor/base.rb +31 -9
- data/lib/thor/command.rb +9 -7
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +12 -0
- data/lib/thor/error.rb +82 -0
- data/lib/thor/group.rb +3 -3
- data/lib/thor/line_editor/basic.rb +2 -0
- data/lib/thor/parser/option.rb +5 -5
- data/lib/thor/parser/options.rb +13 -7
- data/lib/thor/runner.rb +4 -2
- data/lib/thor/shell.rb +1 -1
- data/lib/thor/shell/basic.rb +62 -16
- data/lib/thor/util.rb +1 -1
- data/lib/thor/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b35ad01aa15321a80d1e8c579b3c979c3ff6985a98cca96ffc3e21beba4dd7b5
|
4
|
+
data.tar.gz: f6aa82269a71a418fa99f82ed56672a32da5ce7ebaa4f6f9e2bdb8a16459ec95
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a405c6ac1be920be0d5f1dfe2f4d01ddae7fd68f229d6968c619e243abedfe6adb6b5b58d935b5a8528b4561c6f16fbd9c9ca58befd993ea0bb43950333983e
|
7
|
+
data.tar.gz: 659d3e822725f0bc9161feb69fc2449dfd4b355279a0c59406abf909f23435f157e97b353deec047cd3dbce38a5214d3cd6010844ffd86f50e797a0ab21eac55
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,44 @@
|
|
1
|
+
# 0.20.3
|
2
|
+
* Support old versions of `did_you_mean`.
|
3
|
+
|
4
|
+
# 0.20.2
|
5
|
+
* Fix `did_you_mean` support.
|
6
|
+
|
7
|
+
# 0.20.1
|
8
|
+
* Support new versions fo ERB.
|
9
|
+
* 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
|
+
* Add `did_you_mean` support.
|
11
|
+
|
12
|
+
## 0.20.0
|
13
|
+
* Add `check_default_type!` to check if the default value of an option matches the defined type.
|
14
|
+
It removes the warning on usage and gives the command authors the possibility to check for programming errors.
|
15
|
+
|
16
|
+
* Add `disable_required_check!` to disable check for required options in some commands.
|
17
|
+
It is a substitute of `disable_class_options` that was not working as intended.
|
18
|
+
|
19
|
+
* Add `inject_into_module`.
|
20
|
+
|
21
|
+
## 0.19.4, release 2016-11-28
|
22
|
+
* Rename `Thor::Base#thor_reserved_word?` to `#is_thor_reserved_word?`
|
23
|
+
|
24
|
+
## 0.19.3, release 2016-11-27
|
25
|
+
* Output a warning instead of raising an exception when a default option value doesn't match its specified type
|
26
|
+
|
27
|
+
## 0.19.2, release 2016-11-26
|
28
|
+
* Fix bug with handling of colors passed to `ask` (and methods like `yes?` and `no?` which it underpins)
|
29
|
+
* Allow numeric arguments to be negative
|
30
|
+
* Ensure that default option values are of the specified type (e.g. you can't specify `"foo"` as the default for a numeric option), but make symbols and strings interchangeable
|
31
|
+
* Add `Thor::Shell::Basic#indent` method for intending output
|
32
|
+
* Fix `remove_command` for an inherited command (see #451)
|
33
|
+
* Allow hash arguments to only have each key provided once (see #455)
|
34
|
+
* Allow commands to disable class options, for instance for "help" commands (see #363)
|
35
|
+
* Do not generate a negative option (`--no-no-foo`) for already negative boolean options (`--no-foo`)
|
36
|
+
* Improve compatibility of `Thor::CoreExt::HashWithIndifferentAccess` with Ruby standard library `Hash`
|
37
|
+
* Allow specifying a custom binding for template evaluation (e.g. `#key?` and `#fetch`)
|
38
|
+
* Fix support for subcommand-specific "help"s
|
39
|
+
* Use a string buffer when handling ERB for Ruby 2.3 compatibility
|
40
|
+
* Update dependencies
|
41
|
+
|
1
42
|
## 0.19.1, release 2014-03-24
|
2
43
|
* Fix `say` non-String break regression
|
3
44
|
|
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
|
------------
|
data/lib/thor.rb
CHANGED
@@ -158,10 +158,6 @@ class Thor
|
|
158
158
|
end
|
159
159
|
alias_method :option, :method_option
|
160
160
|
|
161
|
-
def disable_class_options
|
162
|
-
@disable_class_options = true
|
163
|
-
end
|
164
|
-
|
165
161
|
# Prints help information for the given command.
|
166
162
|
#
|
167
163
|
# ==== Parameters
|
@@ -241,6 +237,9 @@ class Thor
|
|
241
237
|
invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h")
|
242
238
|
invoke subcommand_class, *invoke_args
|
243
239
|
end
|
240
|
+
subcommand_class.commands.each do |_meth, command|
|
241
|
+
command.ancestor_name = subcommand
|
242
|
+
end
|
244
243
|
end
|
245
244
|
alias_method :subtask, :subcommand
|
246
245
|
|
@@ -326,12 +325,31 @@ class Thor
|
|
326
325
|
command && stop_on_unknown_option.include?(command.name.to_sym)
|
327
326
|
end
|
328
327
|
|
328
|
+
# Disable the check for required options for the given commands.
|
329
|
+
# This is useful if you have a command that does not need the required options
|
330
|
+
# to work, like help.
|
331
|
+
#
|
332
|
+
# ==== Parameters
|
333
|
+
# Symbol ...:: A list of commands that should be affected.
|
334
|
+
def disable_required_check!(*command_names)
|
335
|
+
disable_required_check.merge(command_names)
|
336
|
+
end
|
337
|
+
|
338
|
+
def disable_required_check?(command) #:nodoc:
|
339
|
+
command && disable_required_check.include?(command.name.to_sym)
|
340
|
+
end
|
341
|
+
|
329
342
|
protected
|
330
343
|
|
331
344
|
def stop_on_unknown_option #:nodoc:
|
332
345
|
@stop_on_unknown_option ||= Set.new
|
333
346
|
end
|
334
347
|
|
348
|
+
# help command has the required check disabled by default.
|
349
|
+
def disable_required_check #:nodoc:
|
350
|
+
@disable_required_check ||= Set.new([:help])
|
351
|
+
end
|
352
|
+
|
335
353
|
# The method responsible for dispatching given the args.
|
336
354
|
def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength
|
337
355
|
meth ||= retrieve_command_name(given_args)
|
@@ -390,12 +408,12 @@ class Thor
|
|
390
408
|
@usage ||= nil
|
391
409
|
@desc ||= nil
|
392
410
|
@long_desc ||= nil
|
393
|
-
@
|
411
|
+
@hide ||= nil
|
394
412
|
|
395
413
|
if @usage && @desc
|
396
414
|
base_class = @hide ? Thor::HiddenCommand : Thor::Command
|
397
|
-
commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options
|
398
|
-
@usage, @desc, @long_desc, @method_options, @hide
|
415
|
+
commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
|
416
|
+
@usage, @desc, @long_desc, @method_options, @hide = nil
|
399
417
|
true
|
400
418
|
elsif all_commands[meth] || meth == "method_missing"
|
401
419
|
true
|
@@ -477,7 +495,6 @@ class Thor
|
|
477
495
|
map HELP_MAPPINGS => :help
|
478
496
|
|
479
497
|
desc "help [COMMAND]", "Describe available commands or one specific command"
|
480
|
-
disable_class_options
|
481
498
|
def help(command = nil, subcommand = false)
|
482
499
|
if command
|
483
500
|
if self.class.subcommands.include? command
|
data/lib/thor/actions.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require "fileutils"
|
2
1
|
require "uri"
|
3
2
|
require "thor/core_ext/io_binary_read"
|
4
3
|
require "thor/actions/create_file"
|
@@ -114,8 +113,10 @@ class Thor
|
|
114
113
|
# the script started).
|
115
114
|
#
|
116
115
|
def relative_to_original_destination_root(path, remove_dot = true)
|
117
|
-
|
118
|
-
if path.
|
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] = '.'
|
119
120
|
remove_dot ? (path[2..-1] || "") : path
|
120
121
|
else
|
121
122
|
path
|
@@ -141,7 +142,7 @@ class Thor
|
|
141
142
|
end
|
142
143
|
end
|
143
144
|
|
144
|
-
message = "Could not find #{file.inspect} in any of your source paths. "
|
145
|
+
message = "Could not find #{file.inspect} in any of your source paths. ".dup
|
145
146
|
|
146
147
|
unless self.class.source_root
|
147
148
|
message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. "
|
@@ -175,6 +176,7 @@ class Thor
|
|
175
176
|
|
176
177
|
# If the directory doesnt exist and we're not pretending
|
177
178
|
if !File.exist?(destination_root) && !pretend
|
179
|
+
require "fileutils"
|
178
180
|
FileUtils.mkdir_p(destination_root)
|
179
181
|
end
|
180
182
|
|
@@ -182,6 +184,7 @@ class Thor
|
|
182
184
|
# In pretend mode, just yield down to the block
|
183
185
|
block.arity == 1 ? yield(destination_root) : yield
|
184
186
|
else
|
187
|
+
require "fileutils"
|
185
188
|
FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
|
186
189
|
end
|
187
190
|
|
@@ -216,6 +219,7 @@ class Thor
|
|
216
219
|
shell.padding += 1 if verbose
|
217
220
|
|
218
221
|
contents = if is_uri
|
222
|
+
require "open-uri"
|
219
223
|
open(path, "Accept" => "application/x-thor-template", &:read)
|
220
224
|
else
|
221
225
|
open(path, &:read)
|
@@ -251,7 +255,16 @@ class Thor
|
|
251
255
|
|
252
256
|
say_status :run, desc, config.fetch(:verbose, true)
|
253
257
|
|
254
|
-
|
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
|
265
|
+
end
|
266
|
+
|
267
|
+
result
|
255
268
|
end
|
256
269
|
|
257
270
|
# Executes a ruby script (taking into account WIN32 platform quirks).
|
@@ -48,12 +48,14 @@ class Thor
|
|
48
48
|
|
49
49
|
def invoke!
|
50
50
|
invoke_with_conflict_check do
|
51
|
+
require "fileutils"
|
51
52
|
::FileUtils.mkdir_p(destination)
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
55
56
|
def revoke!
|
56
57
|
say_status :remove, :red
|
58
|
+
require "fileutils"
|
57
59
|
::FileUtils.rm_rf(destination) if !pretend? && exists?
|
58
60
|
given_destination
|
59
61
|
end
|
@@ -112,11 +114,17 @@ class Thor
|
|
112
114
|
if exists?
|
113
115
|
on_conflict_behavior(&block)
|
114
116
|
else
|
115
|
-
say_status :create, :green
|
116
117
|
yield unless pretend?
|
118
|
+
say_status :create, :green
|
117
119
|
end
|
118
120
|
|
119
121
|
destination
|
122
|
+
rescue Errno::EISDIR, Errno::EEXIST
|
123
|
+
on_file_clash_behavior
|
124
|
+
end
|
125
|
+
|
126
|
+
def on_file_clash_behavior
|
127
|
+
say_status :file_clash, :red
|
120
128
|
end
|
121
129
|
|
122
130
|
# What to do when the destination file already exists.
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require "erb"
|
2
|
-
require "open-uri"
|
3
2
|
|
4
3
|
class Thor
|
5
4
|
module Actions
|
@@ -61,6 +60,9 @@ class Thor
|
|
61
60
|
# destination. If a block is given instead of destination, the content of
|
62
61
|
# the url is yielded and used as location.
|
63
62
|
#
|
63
|
+
# +get+ relies on open-uri, so passing application user input would provide
|
64
|
+
# a command injection attack vector.
|
65
|
+
#
|
64
66
|
# ==== Parameters
|
65
67
|
# source<String>:: the address of the given content.
|
66
68
|
# destination<String>:: the relative path to the destination root.
|
@@ -78,7 +80,12 @@ class Thor
|
|
78
80
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
79
81
|
destination = args.first
|
80
82
|
|
81
|
-
|
83
|
+
if source =~ %r{^https?\://}
|
84
|
+
require "open-uri"
|
85
|
+
else
|
86
|
+
source = File.expand_path(find_in_source_paths(source.to_s))
|
87
|
+
end
|
88
|
+
|
82
89
|
render = open(source) { |input| input.binmode.read }
|
83
90
|
|
84
91
|
destination ||= if block_given?
|
@@ -113,7 +120,15 @@ class Thor
|
|
113
120
|
context = config.delete(:context) || instance_eval("binding")
|
114
121
|
|
115
122
|
create_file destination, nil, config do
|
116
|
-
|
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|
|
130
|
+
erb.filename = source
|
131
|
+
end.result(context)
|
117
132
|
content = yield(content) if block
|
118
133
|
content
|
119
134
|
end
|
@@ -134,7 +149,10 @@ class Thor
|
|
134
149
|
return unless behavior == :invoke
|
135
150
|
path = File.expand_path(path, destination_root)
|
136
151
|
say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
137
|
-
|
152
|
+
unless options[:pretend]
|
153
|
+
require "fileutils"
|
154
|
+
FileUtils.chmod_R(mode, path)
|
155
|
+
end
|
138
156
|
end
|
139
157
|
|
140
158
|
# Prepend text to a file. Since it depends on insert_into_file, it's reversible.
|
@@ -204,6 +222,29 @@ class Thor
|
|
204
222
|
insert_into_file(path, *(args << config), &block)
|
205
223
|
end
|
206
224
|
|
225
|
+
# Injects text right after the module definition. Since it depends on
|
226
|
+
# insert_into_file, it's reversible.
|
227
|
+
#
|
228
|
+
# ==== Parameters
|
229
|
+
# path<String>:: path of the file to be changed
|
230
|
+
# module_name<String|Class>:: the module to be manipulated
|
231
|
+
# data<String>:: the data to append to the class, can be also given as a block.
|
232
|
+
# config<Hash>:: give :verbose => false to not log the status.
|
233
|
+
#
|
234
|
+
# ==== Examples
|
235
|
+
#
|
236
|
+
# inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n"
|
237
|
+
#
|
238
|
+
# inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do
|
239
|
+
# " def help; 'help'; end\n"
|
240
|
+
# end
|
241
|
+
#
|
242
|
+
def inject_into_module(path, module_name, *args, &block)
|
243
|
+
config = args.last.is_a?(Hash) ? args.pop : {}
|
244
|
+
config[:after] = /module #{module_name}\n|module #{module_name} .*\n/
|
245
|
+
insert_into_file(path, *(args << config), &block)
|
246
|
+
end
|
247
|
+
|
207
248
|
# Run a regular expression replacement on a file.
|
208
249
|
#
|
209
250
|
# ==== Parameters
|
@@ -269,7 +310,7 @@ class Thor
|
|
269
310
|
def comment_lines(path, flag, *args)
|
270
311
|
flag = flag.respond_to?(:source) ? flag.source : flag
|
271
312
|
|
272
|
-
gsub_file(path, /^(\s*)([
|
313
|
+
gsub_file(path, /^(\s*)([^#\n]*#{flag})/, '\1# \2', *args)
|
273
314
|
end
|
274
315
|
|
275
316
|
# Removes a file at the given location.
|
@@ -288,7 +329,10 @@ class Thor
|
|
288
329
|
path = File.expand_path(path, destination_root)
|
289
330
|
|
290
331
|
say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
291
|
-
|
332
|
+
if !options[:pretend] && File.exist?(path)
|
333
|
+
require "fileutils"
|
334
|
+
::FileUtils.rm_rf(path)
|
335
|
+
end
|
292
336
|
end
|
293
337
|
alias_method :remove_dir, :remove_file
|
294
338
|
|
@@ -305,8 +349,10 @@ class Thor
|
|
305
349
|
with_output_buffer { yield(*args) }
|
306
350
|
end
|
307
351
|
|
308
|
-
def with_output_buffer(buf = "") #:nodoc:
|
309
|
-
|
352
|
+
def with_output_buffer(buf = "".dup) #:nodoc:
|
353
|
+
raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen?
|
354
|
+
old_buffer = output_buffer
|
355
|
+
self.output_buffer = buf
|
310
356
|
yield
|
311
357
|
output_buffer
|
312
358
|
ensure
|
@@ -319,7 +365,7 @@ class Thor
|
|
319
365
|
def set_eoutvar(compiler, eoutvar = "_erbout")
|
320
366
|
compiler.put_cmd = "#{eoutvar}.concat"
|
321
367
|
compiler.insert_cmd = "#{eoutvar}.concat"
|
322
|
-
compiler.pre_cmd = ["#{eoutvar} = ''"]
|
368
|
+
compiler.pre_cmd = ["#{eoutvar} = ''.dup"]
|
323
369
|
compiler.post_cmd = [eoutvar]
|
324
370
|
end
|
325
371
|
end
|
@@ -53,7 +53,13 @@ class Thor
|
|
53
53
|
replacement + '\0'
|
54
54
|
end
|
55
55
|
|
56
|
-
|
56
|
+
if exists?
|
57
|
+
replace!(/#{flag}/, content, config[:force])
|
58
|
+
else
|
59
|
+
unless pretend?
|
60
|
+
raise Thor::Error, "The file #{ destination } does not appear to exist"
|
61
|
+
end
|
62
|
+
end
|
57
63
|
end
|
58
64
|
|
59
65
|
def revoke!
|
@@ -91,8 +97,8 @@ class Thor
|
|
91
97
|
# Adds the content to the file.
|
92
98
|
#
|
93
99
|
def replace!(regexp, string, force)
|
94
|
-
return if
|
95
|
-
content = File.
|
100
|
+
return if pretend?
|
101
|
+
content = File.read(destination)
|
96
102
|
if force || !content.include?(replacement)
|
97
103
|
content.gsub!(regexp, string)
|
98
104
|
File.open(destination, "wb") { |file| file.write(content) }
|
data/lib/thor/base.rb
CHANGED
@@ -42,7 +42,7 @@ class Thor
|
|
42
42
|
# config<Hash>:: Configuration for this Thor class.
|
43
43
|
#
|
44
44
|
def initialize(args = [], local_options = {}, config = {})
|
45
|
-
parse_options =
|
45
|
+
parse_options = self.class.class_options
|
46
46
|
|
47
47
|
# The start method splits inbound arguments at the first argument
|
48
48
|
# that looks like an option (starts with - or --). It then calls
|
@@ -65,7 +65,8 @@ class Thor
|
|
65
65
|
# declared options from the array. This will leave us with
|
66
66
|
# a list of arguments that weren't declared.
|
67
67
|
stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command]
|
68
|
-
|
68
|
+
disable_required_check = self.class.disable_required_check? config[:current_command]
|
69
|
+
opts = Thor::Options.new(parse_options, hash_options, stop_on_unknown, disable_required_check)
|
69
70
|
self.options = opts.parse(array_options)
|
70
71
|
self.options = config[:class_options].merge(options) if config[:class_options]
|
71
72
|
|
@@ -112,7 +113,7 @@ class Thor
|
|
112
113
|
end
|
113
114
|
|
114
115
|
# Whenever a class inherits from Thor or Thor::Group, we should track the
|
115
|
-
# class and the file on Thor::Base. This is the method
|
116
|
+
# class and the file on Thor::Base. This is the method responsible for it.
|
116
117
|
#
|
117
118
|
def register_klass_file(klass) #:nodoc:
|
118
119
|
file = caller[1].match(/(.*):\d+/)[1]
|
@@ -150,6 +151,21 @@ class Thor
|
|
150
151
|
!!check_unknown_options
|
151
152
|
end
|
152
153
|
|
154
|
+
# If you want to raise an error when the default value of an option does not match
|
155
|
+
# the type call check_default_type!
|
156
|
+
# This is disabled by default for compatibility.
|
157
|
+
def check_default_type!
|
158
|
+
@check_default_type = true
|
159
|
+
end
|
160
|
+
|
161
|
+
def check_default_type #:nodoc:
|
162
|
+
@check_default_type ||= from_superclass(:check_default_type, false)
|
163
|
+
end
|
164
|
+
|
165
|
+
def check_default_type? #:nodoc:
|
166
|
+
!!check_default_type
|
167
|
+
end
|
168
|
+
|
153
169
|
# If true, option parsing is suspended as soon as an unknown option or a
|
154
170
|
# regular argument is encountered. All remaining arguments are passed to
|
155
171
|
# the command as regular arguments.
|
@@ -157,6 +173,12 @@ class Thor
|
|
157
173
|
false
|
158
174
|
end
|
159
175
|
|
176
|
+
# If true, option set will not suspend the execution of the command when
|
177
|
+
# a required option is not provided.
|
178
|
+
def disable_required_check?(command_name) #:nodoc:
|
179
|
+
false
|
180
|
+
end
|
181
|
+
|
160
182
|
# If you want only strict string args (useful when cascading thor classes),
|
161
183
|
# call strict_args_position! This is disabled by default to allow dynamic
|
162
184
|
# invocations.
|
@@ -444,13 +466,13 @@ class Thor
|
|
444
466
|
dispatch(nil, given_args.dup, nil, config)
|
445
467
|
rescue Thor::Error => e
|
446
468
|
config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
|
447
|
-
exit(
|
469
|
+
exit(false) if exit_on_failure?
|
448
470
|
rescue Errno::EPIPE
|
449
471
|
# This happens if a thor command is piped to something like `head`,
|
450
472
|
# which closes the pipe when it's done reading. This will also
|
451
473
|
# mean that if the pipe is closed, further unnecessary
|
452
474
|
# computation will not occur.
|
453
|
-
exit(
|
475
|
+
exit(true)
|
454
476
|
end
|
455
477
|
|
456
478
|
# Allows to use private methods from parent in child classes as commands.
|
@@ -471,13 +493,13 @@ class Thor
|
|
471
493
|
alias_method :public_task, :public_command
|
472
494
|
|
473
495
|
def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
|
474
|
-
raise UndefinedCommandError,
|
475
|
-
raise UndefinedCommandError, "Could not find command #{command.inspect}."
|
496
|
+
raise UndefinedCommandError.new(command, all_commands.keys, (namespace if has_namespace))
|
476
497
|
end
|
477
498
|
alias_method :handle_no_task_error, :handle_no_command_error
|
478
499
|
|
479
500
|
def handle_argument_error(command, error, args, arity) #:nodoc:
|
480
|
-
|
501
|
+
name = [command.ancestor_name, command.name].compact.join(" ")
|
502
|
+
msg = "ERROR: \"#{basename} #{name}\" was called with ".dup
|
481
503
|
msg << "no arguments" if args.empty?
|
482
504
|
msg << "arguments " << args.inspect unless args.empty?
|
483
505
|
msg << "\nUsage: #{banner(command).inspect}"
|
@@ -541,7 +563,7 @@ class Thor
|
|
541
563
|
# options<Hash>:: Described in both class_option and method_option.
|
542
564
|
# scope<Hash>:: Options hash that is being built up
|
543
565
|
def build_option(name, options, scope) #:nodoc:
|
544
|
-
scope[name] = Thor::Option.new(name, options)
|
566
|
+
scope[name] = Thor::Option.new(name, options.merge(:check_default_type => check_default_type?))
|
545
567
|
end
|
546
568
|
|
547
569
|
# Receives a hash of options, parse them and add to the scope. This is a
|
data/lib/thor/command.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
class Thor
|
2
|
-
class Command < Struct.new(:name, :description, :long_description, :usage, :options, :
|
2
|
+
class Command < Struct.new(:name, :description, :long_description, :usage, :options, :ancestor_name)
|
3
3
|
FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
|
4
4
|
|
5
|
-
def initialize(name, description, long_description, usage, options = nil
|
6
|
-
super(name.to_s, description, long_description, usage, options || {}
|
5
|
+
def initialize(name, description, long_description, usage, options = nil)
|
6
|
+
super(name.to_s, description, long_description, usage, options || {})
|
7
7
|
end
|
8
8
|
|
9
9
|
def initialize_copy(other) #:nodoc:
|
@@ -39,13 +39,15 @@ class Thor
|
|
39
39
|
# Returns the formatted usage by injecting given required arguments
|
40
40
|
# and required options into the given usage.
|
41
41
|
def formatted_usage(klass, namespace = true, subcommand = false)
|
42
|
-
if
|
42
|
+
if ancestor_name
|
43
|
+
formatted = "#{ancestor_name} ".dup # add space
|
44
|
+
elsif namespace
|
43
45
|
namespace = klass.namespace
|
44
|
-
formatted = "#{namespace.gsub(/^(default)/, '')}:"
|
46
|
+
formatted = "#{namespace.gsub(/^(default)/, '')}:".dup
|
45
47
|
end
|
46
|
-
formatted
|
48
|
+
formatted ||= "#{klass.namespace.split(':').last} ".dup if subcommand
|
47
49
|
|
48
|
-
formatted ||= ""
|
50
|
+
formatted ||= "".dup
|
49
51
|
|
50
52
|
# Add usage with required arguments
|
51
53
|
formatted << if klass && !klass.arguments.empty?
|
@@ -51,6 +51,18 @@ class Thor
|
|
51
51
|
self
|
52
52
|
end
|
53
53
|
|
54
|
+
def reverse_merge(other)
|
55
|
+
self.class.new(other).merge(self)
|
56
|
+
end
|
57
|
+
|
58
|
+
def reverse_merge!(other_hash)
|
59
|
+
replace(reverse_merge(other_hash))
|
60
|
+
end
|
61
|
+
|
62
|
+
def replace(other_hash)
|
63
|
+
super(other_hash)
|
64
|
+
end
|
65
|
+
|
54
66
|
# Convert to a Hash with String keys.
|
55
67
|
def to_hash
|
56
68
|
Hash.new(default).merge!(self)
|
data/lib/thor/error.rb
CHANGED
@@ -1,4 +1,23 @@
|
|
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
|
20
|
+
|
2
21
|
# Thor::Error is raised when it's caused by wrong usage of thor classes. Those
|
3
22
|
# errors have their backtrace suppressed and are nicely shown to the user.
|
4
23
|
#
|
@@ -10,6 +29,35 @@ class Thor
|
|
10
29
|
|
11
30
|
# Raised when a command was not found.
|
12
31
|
class UndefinedCommandError < Error
|
32
|
+
class SpellChecker
|
33
|
+
attr_reader :error
|
34
|
+
|
35
|
+
def initialize(error)
|
36
|
+
@error = error
|
37
|
+
end
|
38
|
+
|
39
|
+
def corrections
|
40
|
+
@corrections ||= spell_checker.correct(error.command).map(&:inspect)
|
41
|
+
end
|
42
|
+
|
43
|
+
def spell_checker
|
44
|
+
NoKwargSpellChecker.new(error.all_commands)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :command, :all_commands
|
49
|
+
|
50
|
+
def initialize(command, all_commands, namespace)
|
51
|
+
@command = command
|
52
|
+
@all_commands = all_commands
|
53
|
+
|
54
|
+
message = "Could not find command #{command.inspect}"
|
55
|
+
message = namespace ? "#{message} in #{namespace.inspect} namespace." : "#{message}."
|
56
|
+
|
57
|
+
super(message)
|
58
|
+
end
|
59
|
+
|
60
|
+
prepend Correctable if Correctable
|
13
61
|
end
|
14
62
|
UndefinedTaskError = UndefinedCommandError
|
15
63
|
|
@@ -22,6 +70,33 @@ class Thor
|
|
22
70
|
end
|
23
71
|
|
24
72
|
class UnknownArgumentError < Error
|
73
|
+
class SpellChecker
|
74
|
+
attr_reader :error
|
75
|
+
|
76
|
+
def initialize(error)
|
77
|
+
@error = error
|
78
|
+
end
|
79
|
+
|
80
|
+
def corrections
|
81
|
+
@corrections ||=
|
82
|
+
error.unknown.flat_map { |unknown| spell_checker.correct(unknown) }.uniq.map(&:inspect)
|
83
|
+
end
|
84
|
+
|
85
|
+
def spell_checker
|
86
|
+
@spell_checker ||= NoKwargSpellChecker.new(error.switches)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_reader :switches, :unknown
|
91
|
+
|
92
|
+
def initialize(switches, unknown)
|
93
|
+
@switches = switches
|
94
|
+
@unknown = unknown
|
95
|
+
|
96
|
+
super("Unknown switches #{unknown.map(&:inspect).join(', ')}")
|
97
|
+
end
|
98
|
+
|
99
|
+
prepend Correctable if Correctable
|
25
100
|
end
|
26
101
|
|
27
102
|
class RequiredArgumentMissingError < InvocationError
|
@@ -29,4 +104,11 @@ class Thor
|
|
29
104
|
|
30
105
|
class MalformattedArgumentError < InvocationError
|
31
106
|
end
|
107
|
+
|
108
|
+
if Correctable
|
109
|
+
DidYouMean::SPELL_CHECKERS.merge!(
|
110
|
+
'Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker,
|
111
|
+
'Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker
|
112
|
+
)
|
113
|
+
end
|
32
114
|
end
|
data/lib/thor/group.rb
CHANGED
@@ -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
|
data/lib/thor/parser/option.rb
CHANGED
@@ -5,6 +5,7 @@ class Thor
|
|
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)
|
9
10
|
super
|
10
11
|
@lazy_default = options[:lazy_default]
|
@@ -80,12 +81,12 @@ class Thor
|
|
80
81
|
|
81
82
|
def usage(padding = 0)
|
82
83
|
sample = if banner && !banner.to_s.empty?
|
83
|
-
"#{switch_name}=#{banner}"
|
84
|
+
"#{switch_name}=#{banner}".dup
|
84
85
|
else
|
85
86
|
switch_name
|
86
87
|
end
|
87
88
|
|
88
|
-
sample = "[#{sample}]" unless required?
|
89
|
+
sample = "[#{sample}]".dup unless required?
|
89
90
|
|
90
91
|
if boolean?
|
91
92
|
sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-")
|
@@ -110,7 +111,7 @@ class Thor
|
|
110
111
|
|
111
112
|
def validate!
|
112
113
|
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
|
113
|
-
validate_default_type!
|
114
|
+
validate_default_type! if @check_default_type
|
114
115
|
end
|
115
116
|
|
116
117
|
def validate_default_type!
|
@@ -127,8 +128,7 @@ class Thor
|
|
127
128
|
@default.class.name.downcase.to_sym
|
128
129
|
end
|
129
130
|
|
130
|
-
#
|
131
|
-
warn "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type
|
131
|
+
raise ArgumentError, "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type
|
132
132
|
end
|
133
133
|
|
134
134
|
def dasherized?
|
data/lib/thor/parser/options.rb
CHANGED
@@ -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
|
@@ -98,6 +101,7 @@ class Thor
|
|
98
101
|
elsif @stop_on_unknown
|
99
102
|
@parsing_options = false
|
100
103
|
@extra << shifted
|
104
|
+
@stopped_parsing_after_extra_index ||= @extra.size
|
101
105
|
@extra << shift while peek
|
102
106
|
break
|
103
107
|
elsif match
|
@@ -111,7 +115,7 @@ class Thor
|
|
111
115
|
end
|
112
116
|
end
|
113
117
|
|
114
|
-
check_requirement!
|
118
|
+
check_requirement! unless @disable_required_check
|
115
119
|
|
116
120
|
assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
|
117
121
|
assigns.freeze
|
@@ -119,9 +123,11 @@ class Thor
|
|
119
123
|
end
|
120
124
|
|
121
125
|
def check_unknown!
|
126
|
+
to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra
|
127
|
+
|
122
128
|
# an unknown option starts with - or -- and has no more --'s afterward.
|
123
|
-
unknown =
|
124
|
-
raise UnknownArgumentError
|
129
|
+
unknown = to_check.select { |str| str =~ /^--?(?:(?!--).)*$/ }
|
130
|
+
raise UnknownArgumentError.new(@switches.keys, unknown) unless unknown.empty?
|
125
131
|
end
|
126
132
|
|
127
133
|
protected
|
@@ -188,7 +194,7 @@ class Thor
|
|
188
194
|
shift
|
189
195
|
false
|
190
196
|
else
|
191
|
-
|
197
|
+
!no_or_skip?(switch)
|
192
198
|
end
|
193
199
|
else
|
194
200
|
@switches.key?(switch) || !no_or_skip?(switch)
|
data/lib/thor/runner.rb
CHANGED
@@ -2,8 +2,6 @@ require "thor"
|
|
2
2
|
require "thor/group"
|
3
3
|
require "thor/core_ext/io_binary_read"
|
4
4
|
|
5
|
-
require "fileutils"
|
6
|
-
require "open-uri"
|
7
5
|
require "yaml"
|
8
6
|
require "digest/md5"
|
9
7
|
require "pathname"
|
@@ -104,6 +102,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
104
102
|
if package == :file
|
105
103
|
File.open(destination, "w") { |f| f.puts contents }
|
106
104
|
else
|
105
|
+
require "fileutils"
|
107
106
|
FileUtils.cp_r(name, destination)
|
108
107
|
end
|
109
108
|
|
@@ -120,6 +119,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
120
119
|
def uninstall(name)
|
121
120
|
raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
|
122
121
|
say "Uninstalling #{name}."
|
122
|
+
require "fileutils"
|
123
123
|
FileUtils.rm_rf(File.join(thor_root, (thor_yaml[name][:filename]).to_s))
|
124
124
|
|
125
125
|
thor_yaml.delete(name)
|
@@ -138,6 +138,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
138
138
|
self.options = options.merge("as" => name)
|
139
139
|
|
140
140
|
if File.directory? File.expand_path(name)
|
141
|
+
require "fileutils"
|
141
142
|
FileUtils.rm_rf(File.join(thor_root, old_filename))
|
142
143
|
|
143
144
|
thor_yaml.delete(old_filename)
|
@@ -194,6 +195,7 @@ private
|
|
194
195
|
yaml_file = File.join(thor_root, "thor.yml")
|
195
196
|
|
196
197
|
unless File.exist?(yaml_file)
|
198
|
+
require "fileutils"
|
197
199
|
FileUtils.mkdir_p(thor_root)
|
198
200
|
yaml_file = File.join(thor_root, "thor.yml")
|
199
201
|
FileUtils.touch(yaml_file)
|
data/lib/thor/shell.rb
CHANGED
@@ -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
|
data/lib/thor/shell/basic.rb
CHANGED
@@ -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
|
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
|
-
|
229
|
-
|
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? ? "[
|
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 :
|
309
|
+
unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH
|
283
310
|
end
|
284
|
-
result < 10 ?
|
311
|
+
result < 10 ? DEFAULT_TERMINAL_WIDTH : result
|
285
312
|
rescue
|
286
|
-
|
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
|
@@ -431,6 +460,23 @@ class Thor
|
|
431
460
|
end
|
432
461
|
correct_answer
|
433
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
|
434
480
|
end
|
435
481
|
end
|
436
482
|
end
|
data/lib/thor/util.rb
CHANGED
@@ -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
|
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
|
data/lib/thor/version.rb
CHANGED
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.
|
4
|
+
version: 0.20.3
|
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:
|
12
|
+
date: 2018-11-10 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.
|
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.
|