thor 0.16.0 → 1.2.1
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 +7 -0
- data/CONTRIBUTING.md +15 -0
- data/README.md +23 -6
- data/bin/thor +1 -1
- data/lib/thor/actions/create_file.rb +34 -35
- data/lib/thor/actions/create_link.rb +9 -5
- data/lib/thor/actions/directory.rb +33 -23
- data/lib/thor/actions/empty_directory.rb +75 -85
- data/lib/thor/actions/file_manipulation.rb +103 -36
- data/lib/thor/actions/inject_into_file.rb +46 -36
- data/lib/thor/actions.rb +90 -68
- data/lib/thor/base.rb +302 -244
- data/lib/thor/command.rb +142 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +52 -24
- data/lib/thor/error.rb +90 -10
- data/lib/thor/group.rb +70 -74
- data/lib/thor/invocation.rb +63 -55
- data/lib/thor/line_editor/basic.rb +37 -0
- data/lib/thor/line_editor/readline.rb +88 -0
- data/lib/thor/line_editor.rb +17 -0
- data/lib/thor/nested_context.rb +29 -0
- data/lib/thor/parser/argument.rb +24 -28
- data/lib/thor/parser/arguments.rb +110 -102
- data/lib/thor/parser/option.rb +53 -15
- data/lib/thor/parser/options.rb +174 -97
- data/lib/thor/parser.rb +4 -4
- data/lib/thor/rake_compat.rb +12 -11
- data/lib/thor/runner.rb +159 -155
- data/lib/thor/shell/basic.rb +216 -93
- data/lib/thor/shell/color.rb +53 -40
- data/lib/thor/shell/html.rb +61 -58
- data/lib/thor/shell.rb +29 -36
- data/lib/thor/util.rb +231 -213
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +303 -166
- data/thor.gemspec +27 -24
- metadata +36 -226
- data/.gitignore +0 -44
- data/.rspec +0 -2
- data/.travis.yml +0 -7
- data/CHANGELOG.rdoc +0 -134
- data/Gemfile +0 -15
- data/Thorfile +0 -30
- data/bin/rake2thor +0 -86
- data/lib/thor/core_ext/dir_escape.rb +0 -0
- data/lib/thor/core_ext/file_binary_read.rb +0 -9
- data/lib/thor/core_ext/ordered_hash.rb +0 -100
- data/lib/thor/task.rb +0 -132
- data/spec/actions/create_file_spec.rb +0 -170
- data/spec/actions/create_link_spec.rb +0 -81
- data/spec/actions/directory_spec.rb +0 -149
- data/spec/actions/empty_directory_spec.rb +0 -130
- data/spec/actions/file_manipulation_spec.rb +0 -370
- data/spec/actions/inject_into_file_spec.rb +0 -135
- data/spec/actions_spec.rb +0 -331
- data/spec/base_spec.rb +0 -279
- data/spec/core_ext/hash_with_indifferent_access_spec.rb +0 -43
- data/spec/core_ext/ordered_hash_spec.rb +0 -115
- data/spec/exit_condition_spec.rb +0 -19
- data/spec/fixtures/application.rb +0 -2
- data/spec/fixtures/app{1}/README +0 -3
- data/spec/fixtures/bundle/execute.rb +0 -6
- data/spec/fixtures/bundle/main.thor +0 -1
- data/spec/fixtures/doc/%file_name%.rb.tt +0 -1
- data/spec/fixtures/doc/COMMENTER +0 -10
- data/spec/fixtures/doc/README +0 -3
- data/spec/fixtures/doc/block_helper.rb +0 -3
- data/spec/fixtures/doc/components/.empty_directory +0 -0
- data/spec/fixtures/doc/config.rb +0 -1
- data/spec/fixtures/doc/config.yaml.tt +0 -1
- data/spec/fixtures/enum.thor +0 -10
- data/spec/fixtures/group.thor +0 -114
- data/spec/fixtures/invoke.thor +0 -112
- data/spec/fixtures/path with spaces +0 -0
- data/spec/fixtures/script.thor +0 -190
- data/spec/fixtures/task.thor +0 -10
- data/spec/group_spec.rb +0 -216
- data/spec/invocation_spec.rb +0 -100
- data/spec/parser/argument_spec.rb +0 -53
- data/spec/parser/arguments_spec.rb +0 -66
- data/spec/parser/option_spec.rb +0 -202
- data/spec/parser/options_spec.rb +0 -330
- data/spec/rake_compat_spec.rb +0 -72
- data/spec/register_spec.rb +0 -135
- data/spec/runner_spec.rb +0 -241
- data/spec/shell/basic_spec.rb +0 -300
- data/spec/shell/color_spec.rb +0 -81
- data/spec/shell/html_spec.rb +0 -32
- data/spec/shell_spec.rb +0 -47
- data/spec/spec_helper.rb +0 -59
- data/spec/task_spec.rb +0 -80
- data/spec/thor_spec.rb +0 -418
- data/spec/util_spec.rb +0 -196
@@ -1,16 +1,16 @@
|
|
1
|
-
require
|
2
|
-
require 'open-uri'
|
1
|
+
require "erb"
|
3
2
|
|
4
3
|
class Thor
|
5
4
|
module Actions
|
6
|
-
|
7
5
|
# Copies the file from the relative source to the relative destination. If
|
8
6
|
# the destination is not given it's assumed to be equal to the source.
|
9
7
|
#
|
10
8
|
# ==== Parameters
|
11
9
|
# source<String>:: the relative path to the source root.
|
12
10
|
# destination<String>:: the relative path to the destination root.
|
13
|
-
# config<Hash>:: give :verbose => false to not log the status
|
11
|
+
# config<Hash>:: give :verbose => false to not log the status, and
|
12
|
+
# :mode => :preserve, to preserve the file mode from the source.
|
13
|
+
|
14
14
|
#
|
15
15
|
# ==== Examples
|
16
16
|
#
|
@@ -23,11 +23,15 @@ class Thor
|
|
23
23
|
destination = args.first || source
|
24
24
|
source = File.expand_path(find_in_source_paths(source.to_s))
|
25
25
|
|
26
|
-
create_file destination, nil, config do
|
26
|
+
resulting_destination = create_file destination, nil, config do
|
27
27
|
content = File.binread(source)
|
28
|
-
content =
|
28
|
+
content = yield(content) if block
|
29
29
|
content
|
30
30
|
end
|
31
|
+
if config[:mode] == :preserve
|
32
|
+
mode = File.stat(source).mode
|
33
|
+
chmod(resulting_destination, mode, config)
|
34
|
+
end
|
31
35
|
end
|
32
36
|
|
33
37
|
# Links the file from the relative source to the relative destination. If
|
@@ -44,7 +48,7 @@ class Thor
|
|
44
48
|
#
|
45
49
|
# link_file "doc/README"
|
46
50
|
#
|
47
|
-
def link_file(source, *args
|
51
|
+
def link_file(source, *args)
|
48
52
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
49
53
|
destination = args.first || source
|
50
54
|
source = File.expand_path(find_in_source_paths(source.to_s))
|
@@ -56,6 +60,9 @@ class Thor
|
|
56
60
|
# destination. If a block is given instead of destination, the content of
|
57
61
|
# the url is yielded and used as location.
|
58
62
|
#
|
63
|
+
# +get+ relies on open-uri, so passing application user input would provide
|
64
|
+
# a command injection attack vector.
|
65
|
+
#
|
59
66
|
# ==== Parameters
|
60
67
|
# source<String>:: the address of the given content.
|
61
68
|
# destination<String>:: the relative path to the destination root.
|
@@ -73,11 +80,16 @@ class Thor
|
|
73
80
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
74
81
|
destination = args.first
|
75
82
|
|
76
|
-
|
77
|
-
|
83
|
+
render = if source =~ %r{^https?\://}
|
84
|
+
require "open-uri"
|
85
|
+
URI.send(:open, source) { |input| input.binmode.read }
|
86
|
+
else
|
87
|
+
source = File.expand_path(find_in_source_paths(source.to_s))
|
88
|
+
open(source) { |input| input.binmode.read }
|
89
|
+
end
|
78
90
|
|
79
91
|
destination ||= if block_given?
|
80
|
-
block.arity == 1 ?
|
92
|
+
block.arity == 1 ? yield(render) : yield
|
81
93
|
else
|
82
94
|
File.basename(source)
|
83
95
|
end
|
@@ -102,14 +114,22 @@ class Thor
|
|
102
114
|
#
|
103
115
|
def template(source, *args, &block)
|
104
116
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
105
|
-
destination = args.first || source.sub(
|
117
|
+
destination = args.first || source.sub(/#{TEMPLATE_EXTNAME}$/, "")
|
106
118
|
|
107
119
|
source = File.expand_path(find_in_source_paths(source.to_s))
|
108
|
-
context = instance_eval(
|
120
|
+
context = config.delete(:context) || instance_eval("binding")
|
109
121
|
|
110
122
|
create_file destination, nil, config do
|
111
|
-
|
112
|
-
|
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)
|
132
|
+
content = yield(content) if block
|
113
133
|
content
|
114
134
|
end
|
115
135
|
end
|
@@ -123,13 +143,16 @@ class Thor
|
|
123
143
|
#
|
124
144
|
# ==== Example
|
125
145
|
#
|
126
|
-
# chmod "script
|
146
|
+
# chmod "script/server", 0755
|
127
147
|
#
|
128
|
-
def chmod(path, mode, config={})
|
148
|
+
def chmod(path, mode, config = {})
|
129
149
|
return unless behavior == :invoke
|
130
150
|
path = File.expand_path(path, destination_root)
|
131
151
|
say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
132
|
-
|
152
|
+
unless options[:pretend]
|
153
|
+
require "fileutils"
|
154
|
+
FileUtils.chmod_R(mode, path)
|
155
|
+
end
|
133
156
|
end
|
134
157
|
|
135
158
|
# Prepend text to a file. Since it depends on insert_into_file, it's reversible.
|
@@ -149,7 +172,7 @@ class Thor
|
|
149
172
|
#
|
150
173
|
def prepend_to_file(path, *args, &block)
|
151
174
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
152
|
-
config
|
175
|
+
config[:after] = /\A/
|
153
176
|
insert_into_file(path, *(args << config), &block)
|
154
177
|
end
|
155
178
|
alias_method :prepend_file, :prepend_to_file
|
@@ -171,7 +194,7 @@ class Thor
|
|
171
194
|
#
|
172
195
|
def append_to_file(path, *args, &block)
|
173
196
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
174
|
-
config
|
197
|
+
config[:before] = /\z/
|
175
198
|
insert_into_file(path, *(args << config), &block)
|
176
199
|
end
|
177
200
|
alias_method :append_file, :append_to_file
|
@@ -187,15 +210,38 @@ class Thor
|
|
187
210
|
#
|
188
211
|
# ==== Examples
|
189
212
|
#
|
190
|
-
# inject_into_class "app/controllers/application_controller.rb", ApplicationController, " filter_parameter :password\n"
|
213
|
+
# inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " filter_parameter :password\n"
|
191
214
|
#
|
192
|
-
# inject_into_class "app/controllers/application_controller.rb", ApplicationController do
|
215
|
+
# inject_into_class "app/controllers/application_controller.rb", "ApplicationController" do
|
193
216
|
# " filter_parameter :password\n"
|
194
217
|
# end
|
195
218
|
#
|
196
219
|
def inject_into_class(path, klass, *args, &block)
|
197
220
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
198
|
-
config
|
221
|
+
config[:after] = /class #{klass}\n|class #{klass} .*\n/
|
222
|
+
insert_into_file(path, *(args << config), &block)
|
223
|
+
end
|
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/
|
199
245
|
insert_into_file(path, *(args << config), &block)
|
200
246
|
end
|
201
247
|
|
@@ -205,7 +251,8 @@ class Thor
|
|
205
251
|
# path<String>:: path of the file to be changed
|
206
252
|
# flag<Regexp|String>:: the regexp or string to be replaced
|
207
253
|
# replacement<String>:: the replacement, can be also given as a block
|
208
|
-
# config<Hash>:: give :verbose => false to not log the status
|
254
|
+
# config<Hash>:: give :verbose => false to not log the status, and
|
255
|
+
# :force => true, to force the replacement regardles of runner behavior.
|
209
256
|
#
|
210
257
|
# ==== Example
|
211
258
|
#
|
@@ -216,16 +263,17 @@ class Thor
|
|
216
263
|
# end
|
217
264
|
#
|
218
265
|
def gsub_file(path, flag, *args, &block)
|
219
|
-
return unless behavior == :invoke
|
220
266
|
config = args.last.is_a?(Hash) ? args.pop : {}
|
221
267
|
|
268
|
+
return unless behavior == :invoke || config.fetch(:force, false)
|
269
|
+
|
222
270
|
path = File.expand_path(path, destination_root)
|
223
271
|
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
224
272
|
|
225
273
|
unless options[:pretend]
|
226
274
|
content = File.binread(path)
|
227
275
|
content.gsub!(flag, *args, &block)
|
228
|
-
File.open(path,
|
276
|
+
File.open(path, "wb") { |file| file.write(content) }
|
229
277
|
end
|
230
278
|
end
|
231
279
|
|
@@ -245,7 +293,7 @@ class Thor
|
|
245
293
|
def uncomment_lines(path, flag, *args)
|
246
294
|
flag = flag.respond_to?(:source) ? flag.source : flag
|
247
295
|
|
248
|
-
gsub_file(path, /^(\s*)
|
296
|
+
gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args)
|
249
297
|
end
|
250
298
|
|
251
299
|
# Comment all lines matching a given regex. It will leave the space
|
@@ -264,7 +312,7 @@ class Thor
|
|
264
312
|
def comment_lines(path, flag, *args)
|
265
313
|
flag = flag.respond_to?(:source) ? flag.source : flag
|
266
314
|
|
267
|
-
gsub_file(path, /^(\s*)([
|
315
|
+
gsub_file(path, /^(\s*)([^#\n]*#{flag})/, '\1# \2', *args)
|
268
316
|
end
|
269
317
|
|
270
318
|
# Removes a file at the given location.
|
@@ -278,31 +326,50 @@ class Thor
|
|
278
326
|
# remove_file 'README'
|
279
327
|
# remove_file 'app/controllers/application_controller.rb'
|
280
328
|
#
|
281
|
-
def remove_file(path, config={})
|
329
|
+
def remove_file(path, config = {})
|
282
330
|
return unless behavior == :invoke
|
283
|
-
path
|
331
|
+
path = File.expand_path(path, destination_root)
|
284
332
|
|
285
333
|
say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
|
286
|
-
|
334
|
+
if !options[:pretend] && (File.exist?(path) || File.symlink?(path))
|
335
|
+
require "fileutils"
|
336
|
+
::FileUtils.rm_rf(path)
|
337
|
+
end
|
287
338
|
end
|
288
|
-
|
339
|
+
alias_method :remove_dir, :remove_file
|
289
340
|
|
290
|
-
private
|
291
341
|
attr_accessor :output_buffer
|
342
|
+
private :output_buffer, :output_buffer=
|
343
|
+
|
344
|
+
private
|
345
|
+
|
292
346
|
def concat(string)
|
293
347
|
@output_buffer.concat(string)
|
294
348
|
end
|
295
349
|
|
296
|
-
def capture(*args
|
297
|
-
with_output_buffer {
|
350
|
+
def capture(*args)
|
351
|
+
with_output_buffer { yield(*args) }
|
298
352
|
end
|
299
353
|
|
300
|
-
def with_output_buffer(buf =
|
301
|
-
|
354
|
+
def with_output_buffer(buf = "".dup) #:nodoc:
|
355
|
+
raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen?
|
356
|
+
old_buffer = output_buffer
|
357
|
+
self.output_buffer = buf
|
302
358
|
yield
|
303
359
|
output_buffer
|
304
360
|
ensure
|
305
361
|
self.output_buffer = old_buffer
|
306
362
|
end
|
363
|
+
|
364
|
+
# Thor::Actions#capture depends on what kind of buffer is used in ERB.
|
365
|
+
# Thus CapturableERB fixes ERB to use String buffer.
|
366
|
+
class CapturableERB < ERB
|
367
|
+
def set_eoutvar(compiler, eoutvar = "_erbout")
|
368
|
+
compiler.put_cmd = "#{eoutvar}.concat"
|
369
|
+
compiler.insert_cmd = "#{eoutvar}.concat"
|
370
|
+
compiler.pre_cmd = ["#{eoutvar} = ''.dup"]
|
371
|
+
compiler.post_cmd = [eoutvar]
|
372
|
+
end
|
373
|
+
end
|
307
374
|
end
|
308
375
|
end
|
@@ -1,8 +1,7 @@
|
|
1
|
-
|
1
|
+
require_relative "empty_directory"
|
2
2
|
|
3
3
|
class Thor
|
4
4
|
module Actions
|
5
|
-
|
6
5
|
# Injects the given content into a file. Different from gsub_file, this
|
7
6
|
# method is reversible.
|
8
7
|
#
|
@@ -22,12 +21,14 @@ class Thor
|
|
22
21
|
# gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
|
23
22
|
# end
|
24
23
|
#
|
24
|
+
WARNINGS = { unchanged_no_flag: 'File unchanged! The supplied flag value not found!' }
|
25
|
+
|
25
26
|
def insert_into_file(destination, *args, &block)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
data = block_given? ? block : args.shift
|
28
|
+
|
29
|
+
config = args.shift || {}
|
30
|
+
config[:after] = /\z/ unless config.key?(:before) || config.key?(:after)
|
31
|
+
|
31
32
|
action InjectIntoFile.new(self, destination, data, config)
|
32
33
|
end
|
33
34
|
alias_method :inject_into_file, :insert_into_file
|
@@ -36,7 +37,7 @@ class Thor
|
|
36
37
|
attr_reader :replacement, :flag, :behavior
|
37
38
|
|
38
39
|
def initialize(base, destination, data, config)
|
39
|
-
super(base, destination, {
|
40
|
+
super(base, destination, {:verbose => true}.merge(config))
|
40
41
|
|
41
42
|
@behavior, @flag = if @config.key?(:after)
|
42
43
|
[:after, @config.delete(:after)]
|
@@ -49,15 +50,23 @@ class Thor
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def invoke!
|
52
|
-
say_status :invoke
|
53
|
-
|
54
53
|
content = if @behavior == :after
|
55
54
|
'\0' + replacement
|
56
55
|
else
|
57
56
|
replacement + '\0'
|
58
57
|
end
|
59
58
|
|
60
|
-
|
59
|
+
if exists?
|
60
|
+
if replace!(/#{flag}/, content, config[:force])
|
61
|
+
say_status(:invoke)
|
62
|
+
else
|
63
|
+
say_status(:unchanged, warning: WARNINGS[:unchanged_no_flag], color: :red)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
unless pretend?
|
67
|
+
raise Thor::Error, "The file #{ destination } does not appear to exist"
|
68
|
+
end
|
69
|
+
end
|
61
70
|
end
|
62
71
|
|
63
72
|
def revoke!
|
@@ -74,36 +83,37 @@ class Thor
|
|
74
83
|
replace!(regexp, content, true)
|
75
84
|
end
|
76
85
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
else
|
86
|
-
:insert
|
87
|
-
end
|
86
|
+
protected
|
87
|
+
|
88
|
+
def say_status(behavior, warning: nil, color: nil)
|
89
|
+
status = if behavior == :invoke
|
90
|
+
if flag == /\A/
|
91
|
+
:prepend
|
92
|
+
elsif flag == /\z/
|
93
|
+
:append
|
88
94
|
else
|
89
|
-
:
|
95
|
+
:insert
|
90
96
|
end
|
91
|
-
|
92
|
-
|
97
|
+
elsif warning
|
98
|
+
warning
|
99
|
+
else
|
100
|
+
:subtract
|
93
101
|
end
|
94
102
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
105
|
-
end
|
103
|
+
super(status, (color || config[:verbose]))
|
104
|
+
end
|
105
|
+
|
106
|
+
# Adds the content to the file.
|
107
|
+
#
|
108
|
+
def replace!(regexp, string, force)
|
109
|
+
content = File.read(destination)
|
110
|
+
if force || !content.include?(replacement)
|
111
|
+
success = content.gsub!(regexp, string)
|
106
112
|
|
113
|
+
File.open(destination, "wb") { |file| file.write(content) } unless pretend?
|
114
|
+
success
|
115
|
+
end
|
116
|
+
end
|
107
117
|
end
|
108
118
|
end
|
109
119
|
end
|