thor 0.16.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|