millisami-thor 0.14.6
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.
- data/.autotest +8 -0
- data/.gemtest +0 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/CHANGELOG.rdoc +103 -0
- data/Gemfile +11 -0
- data/LICENSE.md +20 -0
- data/README.md +26 -0
- data/Thorfile +13 -0
- data/bin/rake2thor +86 -0
- data/bin/thor +6 -0
- data/lib/thor/actions/create_file.rb +105 -0
- data/lib/thor/actions/create_link.rb +57 -0
- data/lib/thor/actions/directory.rb +93 -0
- data/lib/thor/actions/empty_directory.rb +134 -0
- data/lib/thor/actions/file_manipulation.rb +270 -0
- data/lib/thor/actions/inject_into_file.rb +109 -0
- data/lib/thor/actions.rb +314 -0
- data/lib/thor/base.rb +598 -0
- data/lib/thor/core_ext/file_binary_read.rb +9 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +75 -0
- data/lib/thor/core_ext/ordered_hash.rb +100 -0
- data/lib/thor/error.rb +30 -0
- data/lib/thor/group.rb +276 -0
- data/lib/thor/invocation.rb +168 -0
- data/lib/thor/parser/argument.rb +67 -0
- data/lib/thor/parser/arguments.rb +165 -0
- data/lib/thor/parser/option.rb +120 -0
- data/lib/thor/parser/options.rb +181 -0
- data/lib/thor/parser.rb +4 -0
- data/lib/thor/rake_compat.rb +70 -0
- data/lib/thor/runner.rb +309 -0
- data/lib/thor/shell/basic.rb +302 -0
- data/lib/thor/shell/color.rb +108 -0
- data/lib/thor/shell/html.rb +121 -0
- data/lib/thor/shell.rb +88 -0
- data/lib/thor/task.rb +129 -0
- data/lib/thor/util.rb +229 -0
- data/lib/thor/version.rb +3 -0
- data/lib/thor.rb +336 -0
- data/spec/actions/create_file_spec.rb +170 -0
- data/spec/actions/directory_spec.rb +136 -0
- data/spec/actions/empty_directory_spec.rb +98 -0
- data/spec/actions/file_manipulation_spec.rb +317 -0
- data/spec/actions/inject_into_file_spec.rb +135 -0
- data/spec/actions_spec.rb +322 -0
- data/spec/base_spec.rb +274 -0
- data/spec/core_ext/hash_with_indifferent_access_spec.rb +43 -0
- data/spec/core_ext/ordered_hash_spec.rb +115 -0
- data/spec/fixtures/application.rb +2 -0
- data/spec/fixtures/bundle/execute.rb +6 -0
- data/spec/fixtures/bundle/main.thor +1 -0
- data/spec/fixtures/doc/%file_name%.rb.tt +1 -0
- data/spec/fixtures/doc/README +3 -0
- data/spec/fixtures/doc/block_helper.rb +3 -0
- data/spec/fixtures/doc/components/.empty_directory +0 -0
- data/spec/fixtures/doc/config.rb +1 -0
- data/spec/fixtures/doc/config.yaml.tt +1 -0
- data/spec/fixtures/group.thor +114 -0
- data/spec/fixtures/invoke.thor +112 -0
- data/spec/fixtures/path with spaces +0 -0
- data/spec/fixtures/script.thor +184 -0
- data/spec/fixtures/task.thor +10 -0
- data/spec/group_spec.rb +216 -0
- data/spec/invocation_spec.rb +100 -0
- data/spec/parser/argument_spec.rb +47 -0
- data/spec/parser/arguments_spec.rb +65 -0
- data/spec/parser/option_spec.rb +202 -0
- data/spec/parser/options_spec.rb +329 -0
- data/spec/rake_compat_spec.rb +72 -0
- data/spec/register_spec.rb +92 -0
- data/spec/runner_spec.rb +210 -0
- data/spec/shell/basic_spec.rb +223 -0
- data/spec/shell/color_spec.rb +41 -0
- data/spec/shell/html_spec.rb +27 -0
- data/spec/shell_spec.rb +47 -0
- data/spec/spec_helper.rb +54 -0
- data/spec/task_spec.rb +74 -0
- data/spec/thor_spec.rb +362 -0
- data/spec/util_spec.rb +163 -0
- data/thor.gemspec +25 -0
- metadata +241 -0
data/lib/thor.rb
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
require 'thor/base'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
class << self
|
5
|
+
# Sets the default task when thor is executed without an explicit task to be called.
|
6
|
+
#
|
7
|
+
# ==== Parameters
|
8
|
+
# meth<Symbol>:: name of the defaut task
|
9
|
+
#
|
10
|
+
def default_task(meth=nil)
|
11
|
+
case meth
|
12
|
+
when :none
|
13
|
+
@default_task = 'help'
|
14
|
+
when nil
|
15
|
+
@default_task ||= from_superclass(:default_task, 'help')
|
16
|
+
else
|
17
|
+
@default_task = meth.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Registers another Thor subclass as a command.
|
22
|
+
#
|
23
|
+
# ==== Parameters
|
24
|
+
# klass<Class>:: Thor subclass to register
|
25
|
+
# command<String>:: Subcommand name to use
|
26
|
+
# usage<String>:: Short usage for the subcommand
|
27
|
+
# description<String>:: Description for the subcommand
|
28
|
+
def register(klass, subcommand_name, usage, description, options={})
|
29
|
+
if klass <= Thor::Group
|
30
|
+
desc usage, description, options
|
31
|
+
define_method(subcommand_name) { invoke klass }
|
32
|
+
else
|
33
|
+
desc usage, description, options
|
34
|
+
subcommand subcommand_name, klass
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Defines the usage and the description of the next task.
|
39
|
+
#
|
40
|
+
# ==== Parameters
|
41
|
+
# usage<String>
|
42
|
+
# description<String>
|
43
|
+
# options<String>
|
44
|
+
#
|
45
|
+
def desc(usage, description, options={})
|
46
|
+
if options[:for]
|
47
|
+
task = find_and_refresh_task(options[:for])
|
48
|
+
task.usage = usage if usage
|
49
|
+
task.description = description if description
|
50
|
+
else
|
51
|
+
@usage, @desc, @hide = usage, description, options[:hide] || false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Defines the long description of the next task.
|
56
|
+
#
|
57
|
+
# ==== Parameters
|
58
|
+
# long description<String>
|
59
|
+
#
|
60
|
+
def long_desc(long_description, options={})
|
61
|
+
if options[:for]
|
62
|
+
task = find_and_refresh_task(options[:for])
|
63
|
+
task.long_description = long_description if long_description
|
64
|
+
else
|
65
|
+
@long_desc = long_description
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Maps an input to a task. If you define:
|
70
|
+
#
|
71
|
+
# map "-T" => "list"
|
72
|
+
#
|
73
|
+
# Running:
|
74
|
+
#
|
75
|
+
# thor -T
|
76
|
+
#
|
77
|
+
# Will invoke the list task.
|
78
|
+
#
|
79
|
+
# ==== Parameters
|
80
|
+
# Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
|
81
|
+
#
|
82
|
+
def map(mappings=nil)
|
83
|
+
@map ||= from_superclass(:map, {})
|
84
|
+
|
85
|
+
if mappings
|
86
|
+
mappings.each do |key, value|
|
87
|
+
if key.respond_to?(:each)
|
88
|
+
key.each {|subkey| @map[subkey] = value}
|
89
|
+
else
|
90
|
+
@map[key] = value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@map
|
96
|
+
end
|
97
|
+
|
98
|
+
# Declares the options for the next task to be declared.
|
99
|
+
#
|
100
|
+
# ==== Parameters
|
101
|
+
# Hash[Symbol => Object]:: The hash key is the name of the option and the value
|
102
|
+
# is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
|
103
|
+
# or :required (string). If you give a value, the type of the value is used.
|
104
|
+
#
|
105
|
+
def method_options(options=nil)
|
106
|
+
@method_options ||= {}
|
107
|
+
build_options(options, @method_options) if options
|
108
|
+
@method_options
|
109
|
+
end
|
110
|
+
|
111
|
+
# Adds an option to the set of method options. If :for is given as option,
|
112
|
+
# it allows you to change the options from a previous defined task.
|
113
|
+
#
|
114
|
+
# def previous_task
|
115
|
+
# # magic
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# method_option :foo => :bar, :for => :previous_task
|
119
|
+
#
|
120
|
+
# def next_task
|
121
|
+
# # magic
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# ==== Parameters
|
125
|
+
# name<Symbol>:: The name of the argument.
|
126
|
+
# options<Hash>:: Described below.
|
127
|
+
#
|
128
|
+
# ==== Options
|
129
|
+
# :desc - Description for the argument.
|
130
|
+
# :required - If the argument is required or not.
|
131
|
+
# :default - Default value for this argument. It cannot be required and have default values.
|
132
|
+
# :aliases - Aliases for this option.
|
133
|
+
# :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
|
134
|
+
# :banner - String to show on usage notes.
|
135
|
+
#
|
136
|
+
def method_option(name, options={})
|
137
|
+
scope = if options[:for]
|
138
|
+
find_and_refresh_task(options[:for]).options
|
139
|
+
else
|
140
|
+
method_options
|
141
|
+
end
|
142
|
+
|
143
|
+
build_option(name, options, scope)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Prints help information for the given task.
|
147
|
+
#
|
148
|
+
# ==== Parameters
|
149
|
+
# shell<Thor::Shell>
|
150
|
+
# task_name<String>
|
151
|
+
#
|
152
|
+
def task_help(shell, task_name)
|
153
|
+
meth = normalize_task_name(task_name)
|
154
|
+
task = all_tasks[meth]
|
155
|
+
handle_no_task_error(meth) unless task
|
156
|
+
|
157
|
+
shell.say "Usage:"
|
158
|
+
shell.say " #{banner(task)}"
|
159
|
+
shell.say
|
160
|
+
class_options_help(shell, nil => task.options.map { |_, o| o })
|
161
|
+
if task.long_description
|
162
|
+
shell.say "Description:"
|
163
|
+
shell.print_wrapped(task.long_description, :ident => 2)
|
164
|
+
else
|
165
|
+
shell.say task.description
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Prints help information for this class.
|
170
|
+
#
|
171
|
+
# ==== Parameters
|
172
|
+
# shell<Thor::Shell>
|
173
|
+
#
|
174
|
+
def help(shell, subcommand = false)
|
175
|
+
list = printable_tasks(true, subcommand)
|
176
|
+
Thor::Util.thor_classes_in(self).each do |klass|
|
177
|
+
list += klass.printable_tasks(false)
|
178
|
+
end
|
179
|
+
list.sort!{ |a,b| a[0] <=> b[0] }
|
180
|
+
|
181
|
+
shell.say "Tasks:"
|
182
|
+
shell.print_table(list, :ident => 2, :truncate => true)
|
183
|
+
shell.say
|
184
|
+
class_options_help(shell)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Returns tasks ready to be printed.
|
188
|
+
def printable_tasks(all = true, subcommand = false)
|
189
|
+
(all ? all_tasks : tasks).map do |_, task|
|
190
|
+
next if task.hidden?
|
191
|
+
item = []
|
192
|
+
item << banner(task, false, subcommand)
|
193
|
+
item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
|
194
|
+
item
|
195
|
+
end.compact
|
196
|
+
end
|
197
|
+
|
198
|
+
def subcommands
|
199
|
+
@subcommands ||= from_superclass(:subcommands, [])
|
200
|
+
end
|
201
|
+
|
202
|
+
def subcommand(subcommand, subcommand_class)
|
203
|
+
self.subcommands << subcommand.to_s
|
204
|
+
subcommand_class.subcommand_help subcommand
|
205
|
+
define_method(subcommand) { |*args| invoke subcommand_class, args }
|
206
|
+
end
|
207
|
+
|
208
|
+
# Extend check unknown options to accept a hash of conditions.
|
209
|
+
#
|
210
|
+
# === Parameters
|
211
|
+
# options<Hash>: A hash containing :only and/or :except keys
|
212
|
+
def check_unknown_options!(options={})
|
213
|
+
@check_unknown_options ||= Hash.new
|
214
|
+
options.each do |key, value|
|
215
|
+
if value
|
216
|
+
@check_unknown_options[key] = Array(value)
|
217
|
+
else
|
218
|
+
@check_unknown_options.delete(key)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
@check_unknown_options
|
222
|
+
end
|
223
|
+
|
224
|
+
# Overwrite check_unknown_options? to take subcommands and options into account.
|
225
|
+
def check_unknown_options?(config) #:nodoc:
|
226
|
+
options = check_unknown_options
|
227
|
+
return false unless options
|
228
|
+
|
229
|
+
task = config[:current_task]
|
230
|
+
return true unless task
|
231
|
+
|
232
|
+
name = task.name
|
233
|
+
|
234
|
+
if subcommands.include?(name)
|
235
|
+
false
|
236
|
+
elsif options[:except]
|
237
|
+
!options[:except].include?(name.to_sym)
|
238
|
+
elsif options[:only]
|
239
|
+
options[:only].include?(name.to_sym)
|
240
|
+
else
|
241
|
+
true
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
protected
|
246
|
+
|
247
|
+
# The method responsible for dispatching given the args.
|
248
|
+
def dispatch(meth, given_args, given_opts, config) #:nodoc:
|
249
|
+
meth ||= retrieve_task_name(given_args)
|
250
|
+
task = all_tasks[normalize_task_name(meth)]
|
251
|
+
|
252
|
+
if task
|
253
|
+
args, opts = Thor::Options.split(given_args)
|
254
|
+
else
|
255
|
+
args, opts = given_args, nil
|
256
|
+
task = Thor::DynamicTask.new(meth)
|
257
|
+
end
|
258
|
+
|
259
|
+
opts = given_opts || opts || []
|
260
|
+
config.merge!(:current_task => task, :task_options => task.options)
|
261
|
+
|
262
|
+
instance = new(args, opts, config)
|
263
|
+
args = instance.args
|
264
|
+
trailing = args[Range.new(arguments.size, -1)]
|
265
|
+
instance.invoke_task(task, trailing || [])
|
266
|
+
end
|
267
|
+
|
268
|
+
# The banner for this class. You can customize it if you are invoking the
|
269
|
+
# thor class by another ways which is not the Thor::Runner. It receives
|
270
|
+
# the task that is going to be invoked and a boolean which indicates if
|
271
|
+
# the namespace should be displayed as arguments.
|
272
|
+
#
|
273
|
+
def banner(task, namespace = nil, subcommand = false)
|
274
|
+
"#{basename} #{task.formatted_usage(self, $thor_runner, subcommand)}"
|
275
|
+
end
|
276
|
+
|
277
|
+
def baseclass #:nodoc:
|
278
|
+
Thor
|
279
|
+
end
|
280
|
+
|
281
|
+
def create_task(meth) #:nodoc:
|
282
|
+
if @usage && @desc
|
283
|
+
base_class = @hide ? Thor::HiddenTask : Thor::Task
|
284
|
+
tasks[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
|
285
|
+
@usage, @desc, @long_desc, @method_options, @hide = nil
|
286
|
+
true
|
287
|
+
elsif self.all_tasks[meth] || meth == "method_missing"
|
288
|
+
true
|
289
|
+
else
|
290
|
+
puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
|
291
|
+
"Call desc if you want this method to be available as task or declare it inside a " <<
|
292
|
+
"no_tasks{} block. Invoked from #{caller[1].inspect}."
|
293
|
+
false
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def initialize_added #:nodoc:
|
298
|
+
class_options.merge!(method_options)
|
299
|
+
@method_options = nil
|
300
|
+
end
|
301
|
+
|
302
|
+
# Retrieve the task name from given args.
|
303
|
+
def retrieve_task_name(args) #:nodoc:
|
304
|
+
meth = args.first.to_s unless args.empty?
|
305
|
+
|
306
|
+
if meth && (map[meth] || meth !~ /^\-/)
|
307
|
+
args.shift
|
308
|
+
else
|
309
|
+
nil
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Receives a task name (can be nil), and try to get a map from it.
|
314
|
+
# If a map can't be found use the sent name or the default task.
|
315
|
+
def normalize_task_name(meth) #:nodoc:
|
316
|
+
meth = map[meth.to_s] || meth || default_task
|
317
|
+
meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
|
318
|
+
end
|
319
|
+
|
320
|
+
def subcommand_help(cmd)
|
321
|
+
desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
|
322
|
+
class_eval <<-RUBY
|
323
|
+
def help(task = nil, subcommand = true); super; end
|
324
|
+
RUBY
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
include Thor::Base
|
329
|
+
|
330
|
+
map HELP_MAPPINGS => :help
|
331
|
+
|
332
|
+
desc "help [TASK]", "Describe available tasks or one specific task"
|
333
|
+
def help(task = nil, subcommand = false)
|
334
|
+
task ? self.class.task_help(shell, task) : self.class.help(shell, subcommand)
|
335
|
+
end
|
336
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require 'thor/actions'
|
3
|
+
|
4
|
+
describe Thor::Actions::CreateFile do
|
5
|
+
before(:each) do
|
6
|
+
::FileUtils.rm_rf(destination_root)
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_file(destination=nil, config={}, options={})
|
10
|
+
@base = MyCounter.new([1,2], options, { :destination_root => destination_root })
|
11
|
+
@base.stub!(:file_name).and_return('rdoc')
|
12
|
+
|
13
|
+
@action = Thor::Actions::CreateFile.new(@base, destination, "CONFIGURATION",
|
14
|
+
{ :verbose => !@silence }.merge(config))
|
15
|
+
end
|
16
|
+
|
17
|
+
def invoke!
|
18
|
+
capture(:stdout){ @action.invoke! }
|
19
|
+
end
|
20
|
+
|
21
|
+
def revoke!
|
22
|
+
capture(:stdout){ @action.revoke! }
|
23
|
+
end
|
24
|
+
|
25
|
+
def silence!
|
26
|
+
@silence = true
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#invoke!" do
|
30
|
+
it "creates a file" do
|
31
|
+
create_file("doc/config.rb")
|
32
|
+
invoke!
|
33
|
+
File.exists?(File.join(destination_root, "doc/config.rb")).should be_true
|
34
|
+
end
|
35
|
+
|
36
|
+
it "does not create a file if pretending" do
|
37
|
+
create_file("doc/config.rb", {}, :pretend => true)
|
38
|
+
invoke!
|
39
|
+
File.exists?(File.join(destination_root, "doc/config.rb")).should be_false
|
40
|
+
end
|
41
|
+
|
42
|
+
it "shows created status to the user" do
|
43
|
+
create_file("doc/config.rb")
|
44
|
+
invoke!.should == " create doc/config.rb\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "does not show any information if log status is false" do
|
48
|
+
silence!
|
49
|
+
create_file("doc/config.rb")
|
50
|
+
invoke!.should be_empty
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns the given destination" do
|
54
|
+
capture(:stdout) do
|
55
|
+
create_file("doc/config.rb").invoke!.should == "doc/config.rb"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "converts encoded instructions" do
|
60
|
+
create_file("doc/%file_name%.rb.tt")
|
61
|
+
invoke!
|
62
|
+
File.exists?(File.join(destination_root, "doc/rdoc.rb.tt")).should be_true
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "when file exists" do
|
66
|
+
before(:each) do
|
67
|
+
create_file("doc/config.rb")
|
68
|
+
invoke!
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "and is identical" do
|
72
|
+
it "shows identical status" do
|
73
|
+
create_file("doc/config.rb")
|
74
|
+
invoke!
|
75
|
+
invoke!.should == " identical doc/config.rb\n"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "and is not identical" do
|
80
|
+
before(:each) do
|
81
|
+
File.open(File.join(destination_root, 'doc/config.rb'), 'w'){ |f| f.write("FOO = 3") }
|
82
|
+
end
|
83
|
+
|
84
|
+
it "shows forced status to the user if force is given" do
|
85
|
+
create_file("doc/config.rb", {}, :force => true).should_not be_identical
|
86
|
+
invoke!.should == " force doc/config.rb\n"
|
87
|
+
end
|
88
|
+
|
89
|
+
it "shows skipped status to the user if skip is given" do
|
90
|
+
create_file("doc/config.rb", {}, :skip => true).should_not be_identical
|
91
|
+
invoke!.should == " skip doc/config.rb\n"
|
92
|
+
end
|
93
|
+
|
94
|
+
it "shows forced status to the user if force is configured" do
|
95
|
+
create_file("doc/config.rb", :force => true).should_not be_identical
|
96
|
+
invoke!.should == " force doc/config.rb\n"
|
97
|
+
end
|
98
|
+
|
99
|
+
it "shows skipped status to the user if skip is configured" do
|
100
|
+
create_file("doc/config.rb", :skip => true).should_not be_identical
|
101
|
+
invoke!.should == " skip doc/config.rb\n"
|
102
|
+
end
|
103
|
+
|
104
|
+
it "shows conflict status to ther user" do
|
105
|
+
create_file("doc/config.rb").should_not be_identical
|
106
|
+
$stdin.should_receive(:gets).and_return('s')
|
107
|
+
file = File.join(destination_root, 'doc/config.rb')
|
108
|
+
|
109
|
+
content = invoke!
|
110
|
+
content.should =~ /conflict doc\/config\.rb/
|
111
|
+
content.should =~ /Overwrite #{file}\? \(enter "h" for help\) \[Ynaqdh\]/
|
112
|
+
content.should =~ /skip doc\/config\.rb/
|
113
|
+
end
|
114
|
+
|
115
|
+
it "creates the file if the file collision menu returns true" do
|
116
|
+
create_file("doc/config.rb")
|
117
|
+
$stdin.should_receive(:gets).and_return('y')
|
118
|
+
invoke!.should =~ /force doc\/config\.rb/
|
119
|
+
end
|
120
|
+
|
121
|
+
it "skips the file if the file collision menu returns false" do
|
122
|
+
create_file("doc/config.rb")
|
123
|
+
$stdin.should_receive(:gets).and_return('n')
|
124
|
+
invoke!.should =~ /skip doc\/config\.rb/
|
125
|
+
end
|
126
|
+
|
127
|
+
it "executes the block given to show file content" do
|
128
|
+
create_file("doc/config.rb")
|
129
|
+
$stdin.should_receive(:gets).and_return('d')
|
130
|
+
$stdin.should_receive(:gets).and_return('n')
|
131
|
+
@base.shell.should_receive(:system).with(/diff -u/)
|
132
|
+
invoke!
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "#revoke!" do
|
139
|
+
it "removes the destination file" do
|
140
|
+
create_file("doc/config.rb")
|
141
|
+
invoke!
|
142
|
+
revoke!
|
143
|
+
File.exists?(@action.destination).should be_false
|
144
|
+
end
|
145
|
+
|
146
|
+
it "does not raise an error if the file does not exist" do
|
147
|
+
create_file("doc/config.rb")
|
148
|
+
revoke!
|
149
|
+
File.exists?(@action.destination).should be_false
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "#exists?" do
|
154
|
+
it "returns true if the destination file exists" do
|
155
|
+
create_file("doc/config.rb")
|
156
|
+
@action.exists?.should be_false
|
157
|
+
invoke!
|
158
|
+
@action.exists?.should be_true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "#identical?" do
|
163
|
+
it "returns true if the destination file and is identical" do
|
164
|
+
create_file("doc/config.rb")
|
165
|
+
@action.identical?.should be_false
|
166
|
+
invoke!
|
167
|
+
@action.identical?.should be_true
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require 'thor/actions'
|
3
|
+
|
4
|
+
describe Thor::Actions::Directory do
|
5
|
+
before(:each) do
|
6
|
+
::FileUtils.rm_rf(destination_root)
|
7
|
+
invoker.stub!(:file_name).and_return("rdoc")
|
8
|
+
end
|
9
|
+
|
10
|
+
def invoker
|
11
|
+
@invoker ||= WhinyGenerator.new([1,2], {}, { :destination_root => destination_root })
|
12
|
+
end
|
13
|
+
|
14
|
+
def revoker
|
15
|
+
@revoker ||= WhinyGenerator.new([1,2], {}, { :destination_root => destination_root, :behavior => :revoke })
|
16
|
+
end
|
17
|
+
|
18
|
+
def invoke!(*args, &block)
|
19
|
+
capture(:stdout){ invoker.directory(*args, &block) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def revoke!(*args, &block)
|
23
|
+
capture(:stdout){ revoker.directory(*args, &block) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def exists_and_identical?(source_path, destination_path)
|
27
|
+
%w(config.rb README).each do |file|
|
28
|
+
source = File.join(source_root, source_path, file)
|
29
|
+
destination = File.join(destination_root, destination_path, file)
|
30
|
+
|
31
|
+
File.exists?(destination).should be_true
|
32
|
+
FileUtils.identical?(source, destination).should be_true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#invoke!" do
|
37
|
+
it "raises an error if the source does not exist" do
|
38
|
+
lambda {
|
39
|
+
invoke! "unknown"
|
40
|
+
}.should raise_error(Thor::Error, /Could not find "unknown" in any of your source paths/)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should not create a directory in pretend mode" do
|
44
|
+
invoke! "doc", "ghost", :pretend => true
|
45
|
+
File.exists?("ghost").should be_false
|
46
|
+
end
|
47
|
+
|
48
|
+
it "copies the whole directory recursively to the default destination" do
|
49
|
+
invoke! "doc"
|
50
|
+
exists_and_identical?("doc", "doc")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "copies the whole directory recursively to the specified destination" do
|
54
|
+
invoke! "doc", "docs"
|
55
|
+
exists_and_identical?("doc", "docs")
|
56
|
+
end
|
57
|
+
|
58
|
+
it "copies only the first level files if recursive" do
|
59
|
+
invoke! ".", "tasks", :recursive => false
|
60
|
+
|
61
|
+
file = File.join(destination_root, "tasks", "group.thor")
|
62
|
+
File.exists?(file).should be_true
|
63
|
+
|
64
|
+
file = File.join(destination_root, "tasks", "doc")
|
65
|
+
File.exists?(file).should be_false
|
66
|
+
|
67
|
+
file = File.join(destination_root, "tasks", "doc", "README")
|
68
|
+
File.exists?(file).should be_false
|
69
|
+
end
|
70
|
+
|
71
|
+
it "copies files from the source relative to the current path" do
|
72
|
+
invoker.inside "doc" do
|
73
|
+
invoke! "."
|
74
|
+
end
|
75
|
+
exists_and_identical?("doc", "doc")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "copies and evaluates templates" do
|
79
|
+
invoke! "doc", "docs"
|
80
|
+
file = File.join(destination_root, "docs", "rdoc.rb")
|
81
|
+
File.exists?(file).should be_true
|
82
|
+
File.read(file).should == "FOO = FOO\n"
|
83
|
+
end
|
84
|
+
|
85
|
+
it "copies directories" do
|
86
|
+
invoke! "doc", "docs"
|
87
|
+
file = File.join(destination_root, "docs", "components")
|
88
|
+
File.exists?(file).should be_true
|
89
|
+
File.directory?(file).should be_true
|
90
|
+
end
|
91
|
+
|
92
|
+
it "does not copy .empty_directory files" do
|
93
|
+
invoke! "doc", "docs"
|
94
|
+
file = File.join(destination_root, "docs", "components", ".empty_directory")
|
95
|
+
File.exists?(file).should be_false
|
96
|
+
end
|
97
|
+
|
98
|
+
it "copies directories even if they are empty" do
|
99
|
+
invoke! "doc/components", "docs/components"
|
100
|
+
file = File.join(destination_root, "docs", "components")
|
101
|
+
File.exists?(file).should be_true
|
102
|
+
end
|
103
|
+
|
104
|
+
it "does not copy empty directories twice" do
|
105
|
+
content = invoke!("doc/components", "docs/components")
|
106
|
+
content.should_not =~ /exist/
|
107
|
+
end
|
108
|
+
|
109
|
+
it "logs status" do
|
110
|
+
content = invoke!("doc")
|
111
|
+
content.should =~ /create doc\/README/
|
112
|
+
content.should =~ /create doc\/config\.rb/
|
113
|
+
content.should =~ /create doc\/rdoc\.rb/
|
114
|
+
content.should =~ /create doc\/components/
|
115
|
+
end
|
116
|
+
|
117
|
+
it "yields a block" do
|
118
|
+
checked = false
|
119
|
+
invoke!("doc") do |content|
|
120
|
+
checked ||= !!(content =~ /FOO/)
|
121
|
+
end
|
122
|
+
checked.should be_true
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe "#revoke!" do
|
127
|
+
it "removes the destination file" do
|
128
|
+
invoke! "doc"
|
129
|
+
revoke! "doc"
|
130
|
+
|
131
|
+
File.exists?(File.join(destination_root, "doc", "README")).should be_false
|
132
|
+
File.exists?(File.join(destination_root, "doc", "config.rb")).should be_false
|
133
|
+
File.exists?(File.join(destination_root, "doc", "components")).should be_false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|