thor 0.20.3 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -9
  3. data/lib/thor/actions/create_file.rb +4 -3
  4. data/lib/thor/actions/create_link.rb +3 -2
  5. data/lib/thor/actions/directory.rb +8 -18
  6. data/lib/thor/actions/empty_directory.rb +1 -1
  7. data/lib/thor/actions/file_manipulation.rb +22 -24
  8. data/lib/thor/actions/inject_into_file.rb +34 -13
  9. data/lib/thor/actions.rb +39 -30
  10. data/lib/thor/base.rb +196 -49
  11. data/lib/thor/command.rb +34 -18
  12. data/lib/thor/core_ext/hash_with_indifferent_access.rb +10 -0
  13. data/lib/thor/error.rb +14 -22
  14. data/lib/thor/group.rb +13 -2
  15. data/lib/thor/invocation.rb +2 -1
  16. data/lib/thor/line_editor/basic.rb +1 -1
  17. data/lib/thor/line_editor/readline.rb +6 -6
  18. data/lib/thor/line_editor.rb +2 -2
  19. data/lib/thor/nested_context.rb +29 -0
  20. data/lib/thor/parser/argument.rb +17 -1
  21. data/lib/thor/parser/arguments.rb +35 -15
  22. data/lib/thor/parser/option.rb +45 -13
  23. data/lib/thor/parser/options.rb +79 -11
  24. data/lib/thor/parser.rb +4 -4
  25. data/lib/thor/rake_compat.rb +3 -2
  26. data/lib/thor/runner.rb +43 -32
  27. data/lib/thor/shell/basic.rb +68 -162
  28. data/lib/thor/shell/color.rb +9 -43
  29. data/lib/thor/shell/column_printer.rb +29 -0
  30. data/lib/thor/shell/html.rb +7 -49
  31. data/lib/thor/shell/lcs_diff.rb +49 -0
  32. data/lib/thor/shell/table_printer.rb +118 -0
  33. data/lib/thor/shell/terminal.rb +42 -0
  34. data/lib/thor/shell/wrapped_printer.rb +38 -0
  35. data/lib/thor/shell.rb +5 -5
  36. data/lib/thor/util.rb +25 -8
  37. data/lib/thor/version.rb +1 -1
  38. data/lib/thor.rb +182 -17
  39. data/thor.gemspec +22 -10
  40. metadata +25 -11
  41. data/CHANGELOG.md +0 -204
  42. data/lib/thor/core_ext/io_binary_read.rb +0 -12
  43. data/lib/thor/core_ext/ordered_hash.rb +0 -129
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b35ad01aa15321a80d1e8c579b3c979c3ff6985a98cca96ffc3e21beba4dd7b5
4
- data.tar.gz: f6aa82269a71a418fa99f82ed56672a32da5ce7ebaa4f6f9e2bdb8a16459ec95
3
+ metadata.gz: 9b49f263d36f84d82f17f16852671ff9e2f529ea0adec4b664a2b8660923d091
4
+ data.tar.gz: 2df9cade7c368e064377ec0f38737d5b379b38265f23d878cb4976588b94564c
5
5
  SHA512:
6
- metadata.gz: 3a405c6ac1be920be0d5f1dfe2f4d01ddae7fd68f229d6968c619e243abedfe6adb6b5b58d935b5a8528b4561c6f16fbd9c9ca58befd993ea0bb43950333983e
7
- data.tar.gz: 659d3e822725f0bc9161feb69fc2449dfd4b355279a0c59406abf909f23435f157e97b353deec047cd3dbce38a5214d3cd6010844ffd86f50e797a0ab21eac55
6
+ metadata.gz: c4cca8a5e388509dd8a45a6484c13fa80d89f15c7bfde65ccc3d48d3f86c299269ce6019af1dfe3d2f6f172aff5ae7d7ef8f49ca69de189dcd721d5c9d48269f
7
+ data.tar.gz: 85b9b4834a91e7fab98ee9f555461cee740bebcbcee3e0efffc9d117d1b8dce6f9967db594225cd32a3d82462b5edc2d6e4c77bb4ef799d6253f3e66f4f88042
data/README.md CHANGED
@@ -2,14 +2,8 @@ Thor
2
2
  ====
3
3
 
4
4
  [![Gem Version](http://img.shields.io/gem/v/thor.svg)][gem]
5
- [![Build Status](http://img.shields.io/travis/erikhuda/thor.svg)][travis]
6
- [![Code Climate](http://img.shields.io/codeclimate/github/erikhuda/thor.svg)][codeclimate]
7
- [![Coverage Status](http://img.shields.io/coveralls/erikhuda/thor.svg)][coveralls]
8
5
 
9
6
  [gem]: https://rubygems.org/gems/thor
10
- [travis]: http://travis-ci.org/erikhuda/thor
11
- [codeclimate]: https://codeclimate.com/github/erikhuda/thor
12
- [coveralls]: https://coveralls.io/r/erikhuda/thor
13
7
 
14
8
  Description
15
9
  -----------
@@ -21,7 +15,7 @@ users.
21
15
 
22
16
  Please note: Thor, by design, is a system tool created to allow seamless file and url
23
17
  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
18
+ which, combined with application user input, would provide a command injection attack
25
19
  vector.
26
20
 
27
21
  [rake]: https://github.com/ruby/rake
@@ -33,9 +27,9 @@ Installation
33
27
 
34
28
  Usage and documentation
35
29
  -----------------------
36
- Please see the [wiki][] for basic usage and other documentation on using Thor. You can also checkout the [official homepage][homepage].
30
+ Please see the [wiki][] for basic usage and other documentation on using Thor. You can also check out the [official homepage][homepage].
37
31
 
38
- [wiki]: https://github.com/erikhuda/thor/wiki
32
+ [wiki]: https://github.com/rails/thor/wiki
39
33
  [homepage]: http://whatisthor.com/
40
34
 
41
35
  Contributing
@@ -1,4 +1,4 @@
1
- require "thor/actions/empty_directory"
1
+ require_relative "empty_directory"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -43,7 +43,8 @@ class Thor
43
43
  # Boolean:: true if it is identical, false otherwise.
44
44
  #
45
45
  def identical?
46
- exists? && File.binread(destination) == render
46
+ # binread uses ASCII-8BIT, so to avoid false negatives, the string must use the same
47
+ exists? && File.binread(destination) == String.new(render).force_encoding("ASCII-8BIT")
47
48
  end
48
49
 
49
50
  # Holds the content to be added to the file.
@@ -60,7 +61,7 @@ class Thor
60
61
  invoke_with_conflict_check do
61
62
  require "fileutils"
62
63
  FileUtils.mkdir_p(File.dirname(destination))
63
- File.open(destination, "wb") { |f| f.write render }
64
+ File.open(destination, "wb", config[:perm]) { |f| f.write render }
64
65
  end
65
66
  given_destination
66
67
  end
@@ -1,4 +1,4 @@
1
- require "thor/actions/create_file"
1
+ require_relative "create_file"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -33,7 +33,8 @@ class Thor
33
33
  # Boolean:: true if it is identical, false otherwise.
34
34
  #
35
35
  def identical?
36
- exists? && File.identical?(render, destination)
36
+ source = File.expand_path(render, File.dirname(destination))
37
+ exists? && File.identical?(source, destination)
37
38
  end
38
39
 
39
40
  def invoke!
@@ -1,4 +1,4 @@
1
- require "thor/actions/empty_directory"
1
+ require_relative "empty_directory"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -56,9 +56,9 @@ class Thor
56
56
  attr_reader :source
57
57
 
58
58
  def initialize(base, source, destination = nil, config = {}, &block)
59
- @source = File.expand_path(base.find_in_source_paths(source.to_s))
59
+ @source = File.expand_path(Dir[Util.escape_globs(base.find_in_source_paths(source.to_s))].first)
60
60
  @block = block
61
- super(base, destination, {:recursive => true}.merge(config))
61
+ super(base, destination, {recursive: true}.merge(config))
62
62
  end
63
63
 
64
64
  def invoke!
@@ -96,22 +96,12 @@ class Thor
96
96
  end
97
97
  end
98
98
 
99
- if RUBY_VERSION < "2.0"
100
- def file_level_lookup(previous_lookup)
101
- File.join(previous_lookup, "{*,.[a-z]*}")
102
- end
103
-
104
- def files(lookup)
105
- Dir[lookup]
106
- end
107
- else
108
- def file_level_lookup(previous_lookup)
109
- File.join(previous_lookup, "*")
110
- end
99
+ def file_level_lookup(previous_lookup)
100
+ File.join(previous_lookup, "*")
101
+ end
111
102
 
112
- def files(lookup)
113
- Dir.glob(lookup, File::FNM_DOTMATCH)
114
- end
103
+ def files(lookup)
104
+ Dir.glob(lookup, File::FNM_DOTMATCH)
115
105
  end
116
106
  end
117
107
  end
@@ -33,7 +33,7 @@ class Thor
33
33
  #
34
34
  def initialize(base, destination, config = {})
35
35
  @base = base
36
- @config = {:verbose => true}.merge(config)
36
+ @config = {verbose: true}.merge(config)
37
37
  self.destination = destination
38
38
  end
39
39
 
@@ -10,7 +10,6 @@ class Thor
10
10
  # destination<String>:: the relative path to the destination root.
11
11
  # config<Hash>:: give :verbose => false to not log the status, and
12
12
  # :mode => :preserve, to preserve the file mode from the source.
13
-
14
13
  #
15
14
  # ==== Examples
16
15
  #
@@ -23,14 +22,14 @@ class Thor
23
22
  destination = args.first || source
24
23
  source = File.expand_path(find_in_source_paths(source.to_s))
25
24
 
26
- create_file destination, nil, config do
25
+ resulting_destination = create_file destination, nil, config do
27
26
  content = File.binread(source)
28
27
  content = yield(content) if block
29
28
  content
30
29
  end
31
30
  if config[:mode] == :preserve
32
31
  mode = File.stat(source).mode
33
- chmod(destination, mode, config)
32
+ chmod(resulting_destination, mode, config)
34
33
  end
35
34
  end
36
35
 
@@ -66,12 +65,15 @@ class Thor
66
65
  # ==== Parameters
67
66
  # source<String>:: the address of the given content.
68
67
  # destination<String>:: the relative path to the destination root.
69
- # config<Hash>:: give :verbose => false to not log the status.
68
+ # config<Hash>:: give :verbose => false to not log the status, and
69
+ # :http_headers => <Hash> to add headers to an http request.
70
70
  #
71
71
  # ==== Examples
72
72
  #
73
73
  # get "http://gist.github.com/103208", "doc/README"
74
74
  #
75
+ # get "http://gist.github.com/103208", "doc/README", :http_headers => {"Content-Type" => "application/json"}
76
+ #
75
77
  # get "http://gist.github.com/103208" do |content|
76
78
  # content.split("\n").first
77
79
  # end
@@ -80,14 +82,14 @@ class Thor
80
82
  config = args.last.is_a?(Hash) ? args.pop : {}
81
83
  destination = args.first
82
84
 
83
- if source =~ %r{^https?\://}
85
+ render = if source =~ %r{^https?\://}
84
86
  require "open-uri"
87
+ URI.send(:open, source, config.fetch(:http_headers, {})) { |input| input.binmode.read }
85
88
  else
86
89
  source = File.expand_path(find_in_source_paths(source.to_s))
90
+ File.open(source) { |input| input.binmode.read }
87
91
  end
88
92
 
89
- render = open(source) { |input| input.binmode.read }
90
-
91
93
  destination ||= if block_given?
92
94
  block.arity == 1 ? yield(render) : yield
93
95
  else
@@ -120,12 +122,7 @@ class Thor
120
122
  context = config.delete(:context) || instance_eval("binding")
121
123
 
122
124
  create_file destination, nil, config do
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
125
+ capturable_erb = CapturableERB.new(::File.binread(source), trim_mode: "-", eoutvar: "@output_buffer")
129
126
  content = capturable_erb.tap do |erb|
130
127
  erb.filename = source
131
128
  end.result(context)
@@ -210,9 +207,9 @@ class Thor
210
207
  #
211
208
  # ==== Examples
212
209
  #
213
- # inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n"
210
+ # inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " filter_parameter :password\n"
214
211
  #
215
- # inject_into_class "app/controllers/application_controller.rb", ApplicationController do
212
+ # inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do
216
213
  # " filter_parameter :password\n"
217
214
  # end
218
215
  #
@@ -233,9 +230,9 @@ class Thor
233
230
  #
234
231
  # ==== Examples
235
232
  #
236
- # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper, " def help; 'help'; end\n"
233
+ # inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper", " def help; 'help'; end\n"
237
234
  #
238
- # inject_into_module "app/helpers/application_helper.rb", ApplicationHelper do
235
+ # inject_into_module "app/helpers/application_helper.rb", "ApplicationHelper" do
239
236
  # " def help; 'help'; end\n"
240
237
  # end
241
238
  #
@@ -251,7 +248,8 @@ class Thor
251
248
  # path<String>:: path of the file to be changed
252
249
  # flag<Regexp|String>:: the regexp or string to be replaced
253
250
  # replacement<String>:: the replacement, can be also given as a block
254
- # config<Hash>:: give :verbose => false to not log the status.
251
+ # config<Hash>:: give :verbose => false to not log the status, and
252
+ # :force => true, to force the replacement regardless of runner behavior.
255
253
  #
256
254
  # ==== Example
257
255
  #
@@ -262,9 +260,10 @@ class Thor
262
260
  # end
263
261
  #
264
262
  def gsub_file(path, flag, *args, &block)
265
- return unless behavior == :invoke
266
263
  config = args.last.is_a?(Hash) ? args.pop : {}
267
264
 
265
+ return unless behavior == :invoke || config.fetch(:force, false)
266
+
268
267
  path = File.expand_path(path, destination_root)
269
268
  say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
270
269
 
@@ -275,9 +274,8 @@ class Thor
275
274
  end
276
275
  end
277
276
 
278
- # Uncomment all lines matching a given regex. It will leave the space
279
- # which existed before the comment hash in tact but will remove any spacing
280
- # between the comment hash and the beginning of the line.
277
+ # Uncomment all lines matching a given regex. Preserves indentation before
278
+ # the comment hash and removes the hash and any immediate following space.
281
279
  #
282
280
  # ==== Parameters
283
281
  # path<String>:: path of the file to be changed
@@ -291,7 +289,7 @@ class Thor
291
289
  def uncomment_lines(path, flag, *args)
292
290
  flag = flag.respond_to?(:source) ? flag.source : flag
293
291
 
294
- gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args)
292
+ gsub_file(path, /^(\s*)#[[:blank:]]?(.*#{flag})/, '\1\2', *args)
295
293
  end
296
294
 
297
295
  # Comment all lines matching a given regex. It will leave the space
@@ -329,7 +327,7 @@ class Thor
329
327
  path = File.expand_path(path, destination_root)
330
328
 
331
329
  say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
332
- if !options[:pretend] && File.exist?(path)
330
+ if !options[:pretend] && (File.exist?(path) || File.symlink?(path))
333
331
  require "fileutils"
334
332
  ::FileUtils.rm_rf(path)
335
333
  end
@@ -1,4 +1,4 @@
1
- require "thor/actions/empty_directory"
1
+ require_relative "empty_directory"
2
2
 
3
3
  class Thor
4
4
  module Actions
@@ -21,9 +21,14 @@ class Thor
21
21
  # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
22
22
  # end
23
23
  #
24
+ WARNINGS = {unchanged_no_flag: "File unchanged! Either the supplied flag value not found or the content has already been inserted!"}
25
+
24
26
  def insert_into_file(destination, *args, &block)
25
27
  data = block_given? ? block : args.shift
26
- config = args.shift
28
+
29
+ config = args.shift || {}
30
+ config[:after] = /\z/ unless config.key?(:before) || config.key?(:after)
31
+
27
32
  action InjectIntoFile.new(self, destination, data, config)
28
33
  end
29
34
  alias_method :inject_into_file, :insert_into_file
@@ -32,7 +37,7 @@ class Thor
32
37
  attr_reader :replacement, :flag, :behavior
33
38
 
34
39
  def initialize(base, destination, data, config)
35
- super(base, destination, {:verbose => true}.merge(config))
40
+ super(base, destination, {verbose: true}.merge(config))
36
41
 
37
42
  @behavior, @flag = if @config.key?(:after)
38
43
  [:after, @config.delete(:after)]
@@ -45,8 +50,6 @@ class Thor
45
50
  end
46
51
 
47
52
  def invoke!
48
- say_status :invoke
49
-
50
53
  content = if @behavior == :after
51
54
  '\0' + replacement
52
55
  else
@@ -54,7 +57,13 @@ class Thor
54
57
  end
55
58
 
56
59
  if exists?
57
- replace!(/#{flag}/, content, config[:force])
60
+ if replace!(/#{flag}/, content, config[:force])
61
+ say_status(:invoke)
62
+ elsif replacement_present?
63
+ say_status(:unchanged, color: :blue)
64
+ else
65
+ say_status(:unchanged, warning: WARNINGS[:unchanged_no_flag], color: :red)
66
+ end
58
67
  else
59
68
  unless pretend?
60
69
  raise Thor::Error, "The file #{ destination } does not appear to exist"
@@ -78,7 +87,7 @@ class Thor
78
87
 
79
88
  protected
80
89
 
81
- def say_status(behavior)
90
+ def say_status(behavior, warning: nil, color: nil)
82
91
  status = if behavior == :invoke
83
92
  if flag == /\A/
84
93
  :prepend
@@ -87,21 +96,33 @@ class Thor
87
96
  else
88
97
  :insert
89
98
  end
99
+ elsif warning
100
+ warning
101
+ elsif behavior == :unchanged
102
+ :unchanged
90
103
  else
91
104
  :subtract
92
105
  end
93
106
 
94
- super(status, config[:verbose])
107
+ super(status, (color || config[:verbose]))
108
+ end
109
+
110
+ def content
111
+ @content ||= File.read(destination)
112
+ end
113
+
114
+ def replacement_present?
115
+ content.include?(replacement)
95
116
  end
96
117
 
97
118
  # Adds the content to the file.
98
119
  #
99
120
  def replace!(regexp, string, force)
100
- return if pretend?
101
- content = File.read(destination)
102
- if force || !content.include?(replacement)
103
- content.gsub!(regexp, string)
104
- File.open(destination, "wb") { |file| file.write(content) }
121
+ if force || !replacement_present?
122
+ success = content.gsub!(regexp, string)
123
+
124
+ File.open(destination, "wb") { |file| file.write(content) } unless pretend?
125
+ success
105
126
  end
106
127
  end
107
128
  end
data/lib/thor/actions.rb CHANGED
@@ -1,17 +1,16 @@
1
- require "uri"
2
- require "thor/core_ext/io_binary_read"
3
- require "thor/actions/create_file"
4
- require "thor/actions/create_link"
5
- require "thor/actions/directory"
6
- require "thor/actions/empty_directory"
7
- require "thor/actions/file_manipulation"
8
- require "thor/actions/inject_into_file"
1
+ require_relative "actions/create_file"
2
+ require_relative "actions/create_link"
3
+ require_relative "actions/directory"
4
+ require_relative "actions/empty_directory"
5
+ require_relative "actions/file_manipulation"
6
+ require_relative "actions/inject_into_file"
9
7
 
10
8
  class Thor
11
9
  module Actions
12
10
  attr_accessor :behavior
13
11
 
14
12
  def self.included(base) #:nodoc:
13
+ super(base)
15
14
  base.extend ClassMethods
16
15
  end
17
16
 
@@ -47,17 +46,17 @@ class Thor
47
46
  # Add runtime options that help actions execution.
48
47
  #
49
48
  def add_runtime_options!
50
- class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
51
- :desc => "Overwrite files that already exist"
49
+ class_option :force, type: :boolean, aliases: "-f", group: :runtime,
50
+ desc: "Overwrite files that already exist"
52
51
 
53
- class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
54
- :desc => "Run but do not make any changes"
52
+ class_option :pretend, type: :boolean, aliases: "-p", group: :runtime,
53
+ desc: "Run but do not make any changes"
55
54
 
56
- class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime,
57
- :desc => "Suppress status output"
55
+ class_option :quiet, type: :boolean, aliases: "-q", group: :runtime,
56
+ desc: "Suppress status output"
58
57
 
59
- class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
60
- :desc => "Skip files that already exist"
58
+ class_option :skip, type: :boolean, aliases: "-s", group: :runtime,
59
+ desc: "Skip files that already exist"
61
60
  end
62
61
  end
63
62
 
@@ -114,9 +113,9 @@ class Thor
114
113
  #
115
114
  def relative_to_original_destination_root(path, remove_dot = true)
116
115
  root = @destination_stack[0]
117
- if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ''].include?(path[root.size..root.size])
116
+ if path.start_with?(root) && [File::SEPARATOR, File::ALT_SEPARATOR, nil, ""].include?(path[root.size..root.size])
118
117
  path = path.dup
119
- path[0...root.size] = '.'
118
+ path[0...root.size] = "."
120
119
  remove_dot ? (path[2..-1] || "") : path
121
120
  else
122
121
  path
@@ -162,6 +161,8 @@ class Thor
162
161
  # to the block you provide. The path is set back to the previous path when
163
162
  # the method exits.
164
163
  #
164
+ # Returns the value yielded by the block.
165
+ #
165
166
  # ==== Parameters
166
167
  # dir<String>:: the directory to move to.
167
168
  # config<Hash>:: give :verbose => true to log and use padding.
@@ -174,22 +175,24 @@ class Thor
174
175
  shell.padding += 1 if verbose
175
176
  @destination_stack.push File.expand_path(dir, destination_root)
176
177
 
177
- # If the directory doesnt exist and we're not pretending
178
+ # If the directory doesn't exist and we're not pretending
178
179
  if !File.exist?(destination_root) && !pretend
179
180
  require "fileutils"
180
181
  FileUtils.mkdir_p(destination_root)
181
182
  end
182
183
 
184
+ result = nil
183
185
  if pretend
184
186
  # In pretend mode, just yield down to the block
185
- block.arity == 1 ? yield(destination_root) : yield
187
+ result = block.arity == 1 ? yield(destination_root) : yield
186
188
  else
187
189
  require "fileutils"
188
- FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
190
+ FileUtils.cd(destination_root) { result = block.arity == 1 ? yield(destination_root) : yield }
189
191
  end
190
192
 
191
193
  @destination_stack.pop
192
194
  shell.padding -= 1 if verbose
195
+ result
193
196
  end
194
197
 
195
198
  # Goes to the root and execute the given block.
@@ -220,9 +223,9 @@ class Thor
220
223
 
221
224
  contents = if is_uri
222
225
  require "open-uri"
223
- open(path, "Accept" => "application/x-thor-template", &:read)
226
+ URI.open(path, "Accept" => "application/x-thor-template", &:read)
224
227
  else
225
- open(path, &:read)
228
+ File.open(path, &:read)
226
229
  end
227
230
 
228
231
  instance_eval(contents, path)
@@ -257,13 +260,19 @@ class Thor
257
260
 
258
261
  return if options[:pretend]
259
262
 
260
- result = config[:capture] ? `#{command}` : system(command.to_s)
263
+ env_splat = [config[:env]] if config[:env]
261
264
 
262
- if config[:abort_on_failure]
263
- success = config[:capture] ? $?.success? : result
264
- abort unless success
265
+ if config[:capture]
266
+ require "open3"
267
+ result, status = Open3.capture2e(*env_splat, command.to_s)
268
+ success = status.success?
269
+ else
270
+ result = system(*env_splat, command.to_s)
271
+ success = result
265
272
  end
266
273
 
274
+ abort if !success && config.fetch(:abort_on_failure, self.class.exit_on_failure?)
275
+
267
276
  result
268
277
  end
269
278
 
@@ -275,7 +284,7 @@ class Thor
275
284
  #
276
285
  def run_ruby_script(command, config = {})
277
286
  return unless behavior == :invoke
278
- run command, config.merge(:with => Thor::Util.ruby_command)
287
+ run command, config.merge(with: Thor::Util.ruby_command)
279
288
  end
280
289
 
281
290
  # Run a thor command. A hash of options can be given and it's converted to
@@ -306,7 +315,7 @@ class Thor
306
315
  args.push Thor::Options.to_switches(config)
307
316
  command = args.join(" ").strip
308
317
 
309
- run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture
318
+ run command, with: :thor, verbose: verbose, pretend: pretend, capture: capture
310
319
  end
311
320
 
312
321
  protected
@@ -314,7 +323,7 @@ class Thor
314
323
  # Allow current root to be shared between invocations.
315
324
  #
316
325
  def _shared_configuration #:nodoc:
317
- super.merge!(:destination_root => destination_root)
326
+ super.merge!(destination_root: destination_root)
318
327
  end
319
328
 
320
329
  def _cleanup_options_and_set(options, key) #:nodoc: