thor 0.19.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +53 -0
- data/README.md +6 -2
- data/lib/thor.rb +44 -12
- data/lib/thor/actions.rb +31 -13
- data/lib/thor/actions/create_file.rb +2 -1
- data/lib/thor/actions/create_link.rb +2 -1
- data/lib/thor/actions/directory.rb +7 -17
- data/lib/thor/actions/empty_directory.rb +9 -1
- data/lib/thor/actions/file_manipulation.rb +58 -12
- data/lib/thor/actions/inject_into_file.rb +27 -10
- data/lib/thor/base.rb +75 -41
- data/lib/thor/command.rb +30 -21
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +12 -0
- data/lib/thor/error.rb +78 -0
- data/lib/thor/group.rb +4 -4
- data/lib/thor/invocation.rb +1 -0
- data/lib/thor/line_editor.rb +2 -2
- data/lib/thor/line_editor/basic.rb +2 -0
- data/lib/thor/line_editor/readline.rb +6 -6
- data/lib/thor/nested_context.rb +29 -0
- data/lib/thor/parser.rb +4 -4
- data/lib/thor/parser/arguments.rb +2 -2
- data/lib/thor/parser/option.rb +22 -9
- data/lib/thor/parser/options.rb +25 -9
- data/lib/thor/rake_compat.rb +1 -0
- data/lib/thor/runner.rb +9 -6
- data/lib/thor/shell.rb +4 -4
- data/lib/thor/shell/basic.rb +72 -17
- data/lib/thor/shell/color.rb +6 -2
- data/lib/thor/shell/html.rb +3 -3
- data/lib/thor/util.rb +17 -1
- data/lib/thor/version.rb +1 -1
- data/thor.gemspec +2 -2
- metadata +13 -9
- data/lib/thor/core_ext/io_binary_read.rb +0 -12
- data/lib/thor/core_ext/ordered_hash.rb +0 -129
data/lib/thor/group.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative "base"
|
2
2
|
|
3
3
|
# Thor has a special class called Thor::Group. The main difference to Thor class
|
4
4
|
# is that it invokes all commands at once. It also include some methods that allows
|
@@ -61,7 +61,7 @@ class Thor::Group
|
|
61
61
|
invocations[name] = false
|
62
62
|
invocation_blocks[name] = block if block_given?
|
63
63
|
|
64
|
-
class_eval <<-METHOD, __FILE__, __LINE__
|
64
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
65
65
|
def _invoke_#{name.to_s.gsub(/\W/, '_')}
|
66
66
|
klass, command = self.class.prepare_for_invocation(nil, #{name.inspect})
|
67
67
|
|
@@ -120,7 +120,7 @@ class Thor::Group
|
|
120
120
|
invocations[name] = true
|
121
121
|
invocation_blocks[name] = block if block_given?
|
122
122
|
|
123
|
-
class_eval <<-METHOD, __FILE__, __LINE__
|
123
|
+
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
124
124
|
def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')}
|
125
125
|
return unless options[#{name.inspect}]
|
126
126
|
|
@@ -205,7 +205,7 @@ class Thor::Group
|
|
205
205
|
alias_method :printable_tasks, :printable_commands
|
206
206
|
|
207
207
|
def handle_argument_error(command, error, _args, arity) #:nodoc:
|
208
|
-
msg = "#{basename} #{command.name} takes #{arity} argument"
|
208
|
+
msg = "#{basename} #{command.name} takes #{arity} argument".dup
|
209
209
|
msg << "s" if arity > 1
|
210
210
|
msg << ", but it should not."
|
211
211
|
raise error, msg
|
data/lib/thor/invocation.rb
CHANGED
data/lib/thor/line_editor.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
|
-
begin
|
2
|
-
require "readline"
|
3
|
-
rescue LoadError
|
4
|
-
end
|
5
|
-
|
6
1
|
class Thor
|
7
2
|
module LineEditor
|
8
3
|
class Readline < Basic
|
9
4
|
def self.available?
|
5
|
+
begin
|
6
|
+
require "readline"
|
7
|
+
rescue LoadError
|
8
|
+
end
|
9
|
+
|
10
10
|
Object.const_defined?(:Readline)
|
11
11
|
end
|
12
12
|
|
13
13
|
def readline
|
14
14
|
if echo?
|
15
15
|
::Readline.completion_append_character = nil
|
16
|
-
#
|
16
|
+
# rb-readline does not allow Readline.completion_proc= to receive nil.
|
17
17
|
if complete = completion_proc
|
18
18
|
::Readline.completion_proc = complete
|
19
19
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Thor
|
2
|
+
class NestedContext
|
3
|
+
def initialize
|
4
|
+
@depth = 0
|
5
|
+
end
|
6
|
+
|
7
|
+
def enter
|
8
|
+
push
|
9
|
+
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
pop
|
13
|
+
end
|
14
|
+
|
15
|
+
def entered?
|
16
|
+
@depth > 0
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def push
|
22
|
+
@depth += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def pop
|
26
|
+
@depth -= 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/thor/parser.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require_relative "parser/argument"
|
2
|
+
require_relative "parser/arguments"
|
3
|
+
require_relative "parser/option"
|
4
|
+
require_relative "parser/options"
|
@@ -9,7 +9,7 @@ class Thor
|
|
9
9
|
arguments = []
|
10
10
|
|
11
11
|
args.each do |item|
|
12
|
-
break if item =~ /^-/
|
12
|
+
break if item.is_a?(String) && item =~ /^-/
|
13
13
|
arguments << item
|
14
14
|
end
|
15
15
|
|
@@ -82,7 +82,7 @@ class Thor
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def current_is_value?
|
85
|
-
peek && peek.to_s !~
|
85
|
+
peek && peek.to_s !~ /^-{1,2}\S+/
|
86
86
|
end
|
87
87
|
|
88
88
|
# Runs through the argument array getting strings that contains ":" and
|
data/lib/thor/parser/option.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
class Thor
|
2
2
|
class Option < Argument #:nodoc:
|
3
|
-
attr_reader :aliases, :group, :lazy_default, :hide
|
3
|
+
attr_reader :aliases, :group, :lazy_default, :hide, :repeatable
|
4
4
|
|
5
5
|
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
|
6
6
|
|
7
7
|
def initialize(name, options = {})
|
8
|
+
@check_default_type = options[:check_default_type]
|
8
9
|
options[:required] = false unless options.key?(:required)
|
10
|
+
@repeatable = options.fetch(:repeatable, false)
|
9
11
|
super
|
10
|
-
@lazy_default
|
11
|
-
@group
|
12
|
-
@aliases
|
13
|
-
@hide
|
12
|
+
@lazy_default = options[:lazy_default]
|
13
|
+
@group = options[:group].to_s.capitalize if options[:group]
|
14
|
+
@aliases = Array(options[:aliases])
|
15
|
+
@hide = options[:hide]
|
14
16
|
end
|
15
17
|
|
16
18
|
# This parse quick options given as method_options. It makes several
|
@@ -80,12 +82,12 @@ class Thor
|
|
80
82
|
|
81
83
|
def usage(padding = 0)
|
82
84
|
sample = if banner && !banner.to_s.empty?
|
83
|
-
"#{switch_name}=#{banner}"
|
85
|
+
"#{switch_name}=#{banner}".dup
|
84
86
|
else
|
85
87
|
switch_name
|
86
88
|
end
|
87
89
|
|
88
|
-
sample = "[#{sample}]" unless required?
|
90
|
+
sample = "[#{sample}]".dup unless required?
|
89
91
|
|
90
92
|
if boolean?
|
91
93
|
sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-")
|
@@ -127,8 +129,19 @@ class Thor
|
|
127
129
|
@default.class.name.downcase.to_sym
|
128
130
|
end
|
129
131
|
|
130
|
-
|
131
|
-
|
132
|
+
expected_type = (@repeatable && @type != :hash) ? :array : @type
|
133
|
+
|
134
|
+
if default_type != expected_type
|
135
|
+
err = "Expected #{expected_type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})"
|
136
|
+
|
137
|
+
if @check_default_type
|
138
|
+
raise ArgumentError, err
|
139
|
+
elsif @check_default_type == nil
|
140
|
+
Thor.deprecation_warning "#{err}.\n" +
|
141
|
+
'This will be rejected in the future unless you explicitly pass the options `check_default_type: false`' +
|
142
|
+
' or call `allow_incompatible_default_type!` in your code'
|
143
|
+
end
|
144
|
+
end
|
132
145
|
end
|
133
146
|
|
134
147
|
def dasherized?
|
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
|
@@ -94,10 +97,12 @@ class Thor
|
|
94
97
|
|
95
98
|
switch = normalize_switch(switch)
|
96
99
|
option = switch_option(switch)
|
97
|
-
|
100
|
+
result = parse_peek(switch, option)
|
101
|
+
assign_result!(option, result)
|
98
102
|
elsif @stop_on_unknown
|
99
103
|
@parsing_options = false
|
100
104
|
@extra << shifted
|
105
|
+
@stopped_parsing_after_extra_index ||= @extra.size
|
101
106
|
@extra << shift while peek
|
102
107
|
break
|
103
108
|
elsif match
|
@@ -111,7 +116,7 @@ class Thor
|
|
111
116
|
end
|
112
117
|
end
|
113
118
|
|
114
|
-
check_requirement!
|
119
|
+
check_requirement! unless @disable_required_check
|
115
120
|
|
116
121
|
assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
|
117
122
|
assigns.freeze
|
@@ -119,13 +124,24 @@ class Thor
|
|
119
124
|
end
|
120
125
|
|
121
126
|
def check_unknown!
|
127
|
+
to_check = @stopped_parsing_after_extra_index ? @extra[0...@stopped_parsing_after_extra_index] : @extra
|
128
|
+
|
122
129
|
# an unknown option starts with - or -- and has no more --'s afterward.
|
123
|
-
unknown =
|
124
|
-
raise UnknownArgumentError
|
130
|
+
unknown = to_check.select { |str| str =~ /^--?(?:(?!--).)*$/ }
|
131
|
+
raise UnknownArgumentError.new(@switches.keys, unknown) unless unknown.empty?
|
125
132
|
end
|
126
133
|
|
127
134
|
protected
|
128
135
|
|
136
|
+
def assign_result!(option, result)
|
137
|
+
if option.repeatable && option.type == :hash
|
138
|
+
(@assigns[option.human_name] ||= {}).merge!(result)
|
139
|
+
elsif option.repeatable
|
140
|
+
(@assigns[option.human_name] ||= []) << result
|
141
|
+
else
|
142
|
+
@assigns[option.human_name] = result
|
143
|
+
end
|
144
|
+
end
|
129
145
|
# Check if the current value in peek is a registered switch.
|
130
146
|
#
|
131
147
|
# Two booleans are returned. The first is true if the current value
|
@@ -155,7 +171,7 @@ class Thor
|
|
155
171
|
end
|
156
172
|
|
157
173
|
def switch?(arg)
|
158
|
-
switch_option(normalize_switch(arg))
|
174
|
+
!switch_option(normalize_switch(arg)).nil?
|
159
175
|
end
|
160
176
|
|
161
177
|
def switch_option(arg)
|
@@ -188,7 +204,7 @@ class Thor
|
|
188
204
|
shift
|
189
205
|
false
|
190
206
|
else
|
191
|
-
|
207
|
+
@switches.key?(switch) || !no_or_skip?(switch)
|
192
208
|
end
|
193
209
|
else
|
194
210
|
@switches.key?(switch) || !no_or_skip?(switch)
|
data/lib/thor/rake_compat.rb
CHANGED
@@ -25,6 +25,7 @@ class Thor
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def self.included(base)
|
28
|
+
super(base)
|
28
29
|
# Hack. Make rakefile point to invoker, so rdoc task is generated properly.
|
29
30
|
rakefile = File.basename(caller[0].match(/(.*):\d+/)[1])
|
30
31
|
Rake.application.instance_variable_set(:@rakefile, rakefile)
|
data/lib/thor/runner.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require "thor/core_ext/io_binary_read"
|
1
|
+
require_relative "../thor"
|
2
|
+
require_relative "group"
|
4
3
|
|
5
|
-
require "fileutils"
|
6
|
-
require "open-uri"
|
7
4
|
require "yaml"
|
8
5
|
require "digest/md5"
|
9
6
|
require "pathname"
|
10
7
|
|
11
8
|
class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
9
|
+
autoload :OpenURI, "open-uri"
|
10
|
+
|
12
11
|
map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
|
13
12
|
|
14
13
|
def self.banner(command, all = false, subcommand = false)
|
@@ -104,6 +103,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
104
103
|
if package == :file
|
105
104
|
File.open(destination, "w") { |f| f.puts contents }
|
106
105
|
else
|
106
|
+
require "fileutils"
|
107
107
|
FileUtils.cp_r(name, destination)
|
108
108
|
end
|
109
109
|
|
@@ -112,7 +112,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
112
112
|
|
113
113
|
desc "version", "Show Thor version"
|
114
114
|
def version
|
115
|
-
|
115
|
+
require_relative "version"
|
116
116
|
say "Thor #{Thor::VERSION}"
|
117
117
|
end
|
118
118
|
|
@@ -120,6 +120,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
120
120
|
def uninstall(name)
|
121
121
|
raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
|
122
122
|
say "Uninstalling #{name}."
|
123
|
+
require "fileutils"
|
123
124
|
FileUtils.rm_rf(File.join(thor_root, (thor_yaml[name][:filename]).to_s))
|
124
125
|
|
125
126
|
thor_yaml.delete(name)
|
@@ -138,6 +139,7 @@ class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength
|
|
138
139
|
self.options = options.merge("as" => name)
|
139
140
|
|
140
141
|
if File.directory? File.expand_path(name)
|
142
|
+
require "fileutils"
|
141
143
|
FileUtils.rm_rf(File.join(thor_root, old_filename))
|
142
144
|
|
143
145
|
thor_yaml.delete(old_filename)
|
@@ -194,6 +196,7 @@ private
|
|
194
196
|
yaml_file = File.join(thor_root, "thor.yml")
|
195
197
|
|
196
198
|
unless File.exist?(yaml_file)
|
199
|
+
require "fileutils"
|
197
200
|
FileUtils.mkdir_p(thor_root)
|
198
201
|
yaml_file = File.join(thor_root, "thor.yml")
|
199
202
|
FileUtils.touch(yaml_file)
|
data/lib/thor/shell.rb
CHANGED
@@ -24,9 +24,9 @@ class Thor
|
|
24
24
|
SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
|
25
25
|
attr_writer :shell
|
26
26
|
|
27
|
-
autoload :Basic, "
|
28
|
-
autoload :Color, "
|
29
|
-
autoload :HTML, "
|
27
|
+
autoload :Basic, File.expand_path("shell/basic", __dir__)
|
28
|
+
autoload :Color, File.expand_path("shell/color", __dir__)
|
29
|
+
autoload :HTML, File.expand_path("shell/html", __dir__)
|
30
30
|
|
31
31
|
# Add shell to initialize config values.
|
32
32
|
#
|
@@ -55,7 +55,7 @@ class Thor
|
|
55
55
|
|
56
56
|
# Common methods that are delegated to the shell.
|
57
57
|
SHELL_DELEGATED_METHODS.each do |method|
|
58
|
-
module_eval <<-METHOD, __FILE__, __LINE__
|
58
|
+
module_eval <<-METHOD, __FILE__, __LINE__ + 1
|
59
59
|
def #{method}(*args,&block)
|
60
60
|
shell.#{method}(*args,&block)
|
61
61
|
end
|
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
|
@@ -422,15 +451,41 @@ class Thor
|
|
422
451
|
|
423
452
|
def ask_filtered(statement, color, options)
|
424
453
|
answer_set = options[:limited_to]
|
454
|
+
case_insensitive = options.fetch(:case_insensitive, false)
|
425
455
|
correct_answer = nil
|
426
456
|
until correct_answer
|
427
457
|
answers = answer_set.join(", ")
|
428
458
|
answer = ask_simply("#{statement} [#{answers}]", color, options)
|
429
|
-
correct_answer = answer_set
|
459
|
+
correct_answer = answer_match(answer_set, answer, case_insensitive)
|
430
460
|
say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
|
431
461
|
end
|
432
462
|
correct_answer
|
433
463
|
end
|
464
|
+
|
465
|
+
def answer_match(possibilities, answer, case_insensitive)
|
466
|
+
if case_insensitive
|
467
|
+
possibilities.detect{ |possibility| possibility.downcase == answer.downcase }
|
468
|
+
else
|
469
|
+
possibilities.detect{ |possibility| possibility == answer }
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
def merge(destination, content) #:nodoc:
|
474
|
+
require "tempfile"
|
475
|
+
Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp|
|
476
|
+
temp.write content
|
477
|
+
temp.rewind
|
478
|
+
system %(#{merge_tool} "#{temp.path}" "#{destination}")
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
def merge_tool #:nodoc:
|
483
|
+
@merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool
|
484
|
+
end
|
485
|
+
|
486
|
+
def git_merge_tool #:nodoc:
|
487
|
+
`git config merge.tool`.rstrip rescue ""
|
488
|
+
end
|
434
489
|
end
|
435
490
|
end
|
436
491
|
end
|