thor 0.19.4 → 0.20.3
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 +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]
|
5
5
|
[][travis]
|
6
|
-
[][gemnasium]
|
7
6
|
[][codeclimate]
|
8
7
|
[][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.
|