thor 0.20.3 → 1.3.2

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.
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: