wycats-thor 0.11.4 → 0.11.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGELOG.rdoc +4 -2
  2. data/Thorfile +57 -0
  3. data/VERSION +1 -0
  4. data/lib/thor.rb +13 -6
  5. data/lib/thor/base.rb +4 -1
  6. data/lib/thor/invocation.rb +1 -1
  7. data/lib/thor/rake_compat.rb +67 -0
  8. data/lib/thor/runner.rb +12 -8
  9. data/lib/thor/shell/basic.rb +8 -4
  10. data/lib/thor/task.rb +9 -1
  11. data/lib/thor/util.rb +28 -8
  12. data/spec/actions/create_file_spec.rb +170 -0
  13. data/spec/actions/directory_spec.rb +118 -0
  14. data/spec/actions/empty_directory_spec.rb +91 -0
  15. data/spec/actions/file_manipulation_spec.rb +242 -0
  16. data/spec/actions/inject_into_file_spec.rb +80 -0
  17. data/spec/actions_spec.rb +291 -0
  18. data/spec/base_spec.rb +235 -0
  19. data/spec/core_ext/hash_with_indifferent_access_spec.rb +43 -0
  20. data/spec/core_ext/ordered_hash_spec.rb +115 -0
  21. data/spec/fixtures/bundle/execute.rb +6 -0
  22. data/spec/fixtures/doc/config.rb +1 -0
  23. data/spec/group_spec.rb +177 -0
  24. data/spec/invocation_spec.rb +107 -0
  25. data/spec/parser/argument_spec.rb +47 -0
  26. data/spec/parser/arguments_spec.rb +64 -0
  27. data/spec/parser/option_spec.rb +212 -0
  28. data/spec/parser/options_spec.rb +255 -0
  29. data/spec/rake_compat_spec.rb +64 -0
  30. data/spec/runner_spec.rb +204 -0
  31. data/spec/shell/basic_spec.rb +206 -0
  32. data/spec/shell/color_spec.rb +41 -0
  33. data/spec/shell_spec.rb +25 -0
  34. data/spec/spec_helper.rb +52 -0
  35. data/spec/task_spec.rb +82 -0
  36. data/spec/thor_spec.rb +234 -0
  37. data/spec/util_spec.rb +192 -0
  38. metadata +56 -36
  39. data/Rakefile +0 -6
  40. data/lib/thor/tasks.rb +0 -4
  41. data/lib/thor/tasks/install.rb +0 -35
  42. data/lib/thor/tasks/package.rb +0 -31
  43. data/lib/thor/tasks/spec.rb +0 -70
@@ -0,0 +1,80 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'thor/actions'
3
+
4
+ describe Thor::Actions::InjectIntoFile do
5
+ before(:each) do
6
+ ::FileUtils.rm_rf(destination_root)
7
+ ::FileUtils.cp_r(source_root, destination_root)
8
+ end
9
+
10
+ def invoker(options={})
11
+ @invoker ||= MyCounter.new([1,2], options, { :destination_root => destination_root })
12
+ end
13
+
14
+ def revoker
15
+ @revoker ||= MyCounter.new([1,2], {}, { :destination_root => destination_root, :behavior => :revoke })
16
+ end
17
+
18
+ def invoke!(*args, &block)
19
+ capture(:stdout){ invoker.inject_into_file(*args, &block) }
20
+ end
21
+
22
+ def revoke!(*args, &block)
23
+ capture(:stdout){ revoker.inject_into_file(*args, &block) }
24
+ end
25
+
26
+ def file
27
+ File.join(destination_root, "doc/README")
28
+ end
29
+
30
+ describe "#invoke!" do
31
+ it "changes the file adding content after the flag" do
32
+ invoke! "doc/README", "\nmore content", :after => "__start__"
33
+ File.read(file).must == "__start__\nmore content\nREADME\n__end__\n"
34
+ end
35
+
36
+ it "changes the file adding content before the flag" do
37
+ invoke! "doc/README", "more content\n", :before => "__end__"
38
+ File.read(file).must == "__start__\nREADME\nmore content\n__end__\n"
39
+ end
40
+
41
+ it "accepts data as a block" do
42
+ invoke! "doc/README", :before => "__end__" do
43
+ "more content\n"
44
+ end
45
+
46
+ File.read(file).must == "__start__\nREADME\nmore content\n__end__\n"
47
+ end
48
+
49
+ it "logs status" do
50
+ invoke!("doc/README", "\nmore content", :after => "__start__").must == " inject doc/README\n"
51
+ end
52
+
53
+ it "does not change the file if pretending" do
54
+ invoker :pretend => true
55
+ invoke! "doc/README", "\nmore content", :after => "__start__"
56
+ File.read(file).must == "__start__\nREADME\n__end__\n"
57
+ end
58
+ end
59
+
60
+ describe "#revoke!" do
61
+ it "deinjects the destination file after injection" do
62
+ invoke! "doc/README", "\nmore content", :after => "__start__"
63
+ revoke! "doc/README", "\nmore content", :after => "__start__"
64
+
65
+ File.read(file).must == "__start__\nREADME\n__end__\n"
66
+ end
67
+
68
+ it "deinjects the destination file before injection" do
69
+ invoke! "doc/README", "\nmore content", :before => "__start__"
70
+ revoke! "doc/README", "\nmore content", :before => "__start__"
71
+
72
+ File.read(file).must == "__start__\nREADME\n__end__\n"
73
+ end
74
+
75
+ it "shows progress information to the user" do
76
+ invoke!("doc/README", "\nmore content", :after => "__start__")
77
+ revoke!("doc/README", "\nmore content", :after => "__start__").must == " deinject doc/README\n"
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,291 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Thor::Actions do
4
+ def runner(options={})
5
+ @runner ||= MyCounter.new([1], options, { :destination_root => destination_root })
6
+ end
7
+
8
+ def action(*args, &block)
9
+ capture(:stdout){ runner.send(*args, &block) }
10
+ end
11
+
12
+ def file
13
+ File.join(destination_root, "foo")
14
+ end
15
+
16
+ describe "on include" do
17
+ it "adds runtime options to the base class" do
18
+ MyCounter.class_options.keys.must include(:pretend)
19
+ MyCounter.class_options.keys.must include(:force)
20
+ MyCounter.class_options.keys.must include(:quiet)
21
+ MyCounter.class_options.keys.must include(:skip)
22
+ end
23
+ end
24
+
25
+ describe "#initialize" do
26
+ it "has default behavior invoke" do
27
+ runner.behavior.must == :invoke
28
+ end
29
+
30
+ it "can have behavior revoke" do
31
+ MyCounter.new([1], {}, :behavior => :revoke).behavior.must == :revoke
32
+ end
33
+
34
+ it "when behavior is set to force, overwrite options" do
35
+ runner = MyCounter.new([1], { :force => false, :skip => true }, :behavior => :force)
36
+ runner.behavior.must == :invoke
37
+ runner.options.force.must be_true
38
+ runner.options.skip.must_not be_true
39
+ end
40
+
41
+ it "when behavior is set to skip, overwrite options" do
42
+ runner = MyCounter.new([1], ["--force"], :behavior => :skip)
43
+ runner.behavior.must == :invoke
44
+ runner.options.force.must_not be_true
45
+ runner.options.skip.must be_true
46
+ end
47
+ end
48
+
49
+ describe "accessors" do
50
+ describe "#destination_root=" do
51
+ it "gets the current directory and expands the path to set the root" do
52
+ base = MyCounter.new([1])
53
+ base.destination_root = "here"
54
+ base.destination_root.must == File.expand_path(File.join(File.dirname(__FILE__), "..", "here"))
55
+ end
56
+
57
+ it "does not use the current directory if one is given" do
58
+ base = MyCounter.new([1])
59
+ base.destination_root = "/"
60
+ base.destination_root.must == "/"
61
+ end
62
+
63
+ it "uses the current directory if none is given" do
64
+ base = MyCounter.new([1])
65
+ base.destination_root.must == File.expand_path(File.join(File.dirname(__FILE__), ".."))
66
+ end
67
+ end
68
+
69
+ describe "#relative_to_original_destination_root" do
70
+ it "returns the path relative to the absolute root" do
71
+ runner.relative_to_original_destination_root(file).must == "foo"
72
+ end
73
+
74
+ it "does not remove dot if required" do
75
+ runner.relative_to_original_destination_root(file, false).must == "./foo"
76
+ end
77
+
78
+ it "always use the absolute root" do
79
+ runner.inside("foo") do
80
+ runner.relative_to_original_destination_root(file).must == "foo"
81
+ end
82
+ end
83
+
84
+ describe "#source_paths_for_search" do
85
+ it "add source_root to source_paths_for_search" do
86
+ MyCounter.source_paths_for_search.must include(File.expand_path("fixtures", File.dirname(__FILE__)))
87
+ end
88
+
89
+ it "keeps only current source root in source paths" do
90
+ ClearCounter.source_paths_for_search.must include(File.expand_path("fixtures/bundle", File.dirname(__FILE__)))
91
+ ClearCounter.source_paths_for_search.must_not include(File.expand_path("fixtures", File.dirname(__FILE__)))
92
+ end
93
+
94
+ it "customized source paths should be before source roots" do
95
+ ClearCounter.source_paths_for_search[0].must == File.expand_path("fixtures/doc", File.dirname(__FILE__))
96
+ ClearCounter.source_paths_for_search[1].must == File.expand_path("fixtures/bundle", File.dirname(__FILE__))
97
+ end
98
+
99
+ it "keeps inherited source paths at the end" do
100
+ ClearCounter.source_paths_for_search.last.must == File.expand_path("fixtures/broken", File.dirname(__FILE__))
101
+ end
102
+ end
103
+ end
104
+
105
+ describe "#find_in_source_paths" do
106
+ it "raises an error if source path is empty" do
107
+ lambda {
108
+ A.new.find_in_source_paths("foo")
109
+ }.must raise_error(Thor::Error, /You don't have any source path defined for class A/)
110
+ end
111
+
112
+ it "finds a template inside the source path" do
113
+ runner.find_in_source_paths("doc").must == File.expand_path("doc", source_root)
114
+ lambda { runner.find_in_source_paths("README") }.must raise_error
115
+
116
+ new_path = File.join(source_root, "doc")
117
+ runner.instance_variable_set(:@source_paths, nil)
118
+ runner.source_paths.unshift(new_path)
119
+ runner.find_in_source_paths("README").must == File.expand_path("README", new_path)
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "#inside" do
125
+ it "executes the block inside the given folder" do
126
+ runner.inside("foo") do
127
+ Dir.pwd.must == file
128
+ end
129
+ end
130
+
131
+ it "changes the base root" do
132
+ runner.inside("foo") do
133
+ runner.destination_root.must == file
134
+ end
135
+ end
136
+
137
+ it "creates the directory if it does not exist" do
138
+ runner.inside("foo") do
139
+ File.exists?(file).must be_true
140
+ end
141
+ end
142
+
143
+ describe "when verbose" do
144
+ it "logs status" do
145
+ capture(:stdout) do
146
+ runner.inside("foo", :verbose => true) {}
147
+ end.must =~ /inside foo/
148
+ end
149
+
150
+ it "uses padding in next status" do
151
+ capture(:stdout) do
152
+ runner.inside("foo", :verbose => true) do
153
+ runner.say_status :cool, :padding
154
+ end
155
+ end.must =~ /cool padding/
156
+ end
157
+
158
+ it "removes padding after block" do
159
+ capture(:stdout) do
160
+ runner.inside("foo", :verbose => true) {}
161
+ runner.say_status :no, :padding
162
+ end.must =~ /no padding/
163
+ end
164
+ end
165
+ end
166
+
167
+ describe "#in_root" do
168
+ it "executes the block in the root folder" do
169
+ runner.inside("foo") do
170
+ runner.in_root { Dir.pwd.must == destination_root }
171
+ end
172
+ end
173
+
174
+ it "changes the base root" do
175
+ runner.inside("foo") do
176
+ runner.in_root { runner.destination_root.must == destination_root }
177
+ end
178
+ end
179
+
180
+ it "returns to the previous state" do
181
+ runner.inside("foo") do
182
+ runner.in_root { }
183
+ runner.destination_root.must == file
184
+ end
185
+ end
186
+ end
187
+
188
+ describe "#apply" do
189
+ before(:each) do
190
+ @template = <<-TEMPLATE
191
+ @foo = "FOO"
192
+ say_status :cool, :padding
193
+ TEMPLATE
194
+ @template.instance_eval "def read; self; end" # Make the string respond to read
195
+
196
+ @file = "http://gist.github.com/103208.txt"
197
+ mock(runner).open(@file){ @template }
198
+ end
199
+
200
+ it "opens a file and executes its content in the instance binding" do
201
+ action :apply, @file
202
+ runner.instance_variable_get("@foo").must == "FOO"
203
+ end
204
+
205
+ it "applies padding to the content inside the file" do
206
+ action(:apply, @file).must =~ /cool padding/
207
+ end
208
+
209
+ it "logs its status" do
210
+ action(:apply, @file).must =~ / apply #{@file}\n/
211
+ end
212
+
213
+ it "does not log status" do
214
+ content = action(:apply, @file, :verbose => false)
215
+ content.must =~ /cool padding/
216
+ content.must_not =~ /apply http/
217
+ end
218
+ end
219
+
220
+ describe "#run" do
221
+ before(:each) do
222
+ mock(runner).system("ls")
223
+ end
224
+
225
+ it "executes the command given" do
226
+ action :run, "ls"
227
+ end
228
+
229
+ it "logs status" do
230
+ action(:run, "ls").must == " run ls from \".\"\n"
231
+ end
232
+
233
+ it "does not log status if required" do
234
+ action(:run, "ls", :verbose => false).must be_empty
235
+ end
236
+
237
+ it "accepts a color as status" do
238
+ mock(runner.shell).say_status(:run, 'ls from "."', :yellow)
239
+ action :run, "ls", :verbose => :yellow
240
+ end
241
+ end
242
+
243
+ describe "#run_ruby_script" do
244
+ before(:each) do
245
+ stub(Thor::Util).ruby_command{ "/opt/jruby" }
246
+ mock(runner).system("/opt/jruby script.rb")
247
+ end
248
+
249
+ it "executes the ruby script" do
250
+ action :run_ruby_script, "script.rb"
251
+ end
252
+
253
+ it "logs status" do
254
+ action(:run_ruby_script, "script.rb").must == " run jruby script.rb from \".\"\n"
255
+ end
256
+
257
+ it "does not log status if required" do
258
+ action(:run_ruby_script, "script.rb", :verbose => false).must be_empty
259
+ end
260
+ end
261
+
262
+ describe "#thor" do
263
+ it "executes the thor command" do
264
+ mock(runner).system("thor list")
265
+ action :thor, :list, :verbose => true
266
+ end
267
+
268
+ it "converts extra arguments to command arguments" do
269
+ mock(runner).system("thor list foo bar")
270
+ action :thor, :list, "foo", "bar"
271
+ end
272
+
273
+ it "converts options hash to switches" do
274
+ mock(runner).system("thor list foo bar --foo")
275
+ action :thor, :list, "foo", "bar", :foo => true
276
+
277
+ mock(runner).system("thor list --foo 1 2 3")
278
+ action :thor, :list, :foo => [1,2,3]
279
+ end
280
+
281
+ it "logs status" do
282
+ mock(runner).system("thor list")
283
+ action(:thor, :list).must == " run thor list from \".\"\n"
284
+ end
285
+
286
+ it "does not log status if required" do
287
+ mock(runner).system("thor list --foo 1 2 3")
288
+ action(:thor, :list, :foo => [1,2,3], :verbose => false).must be_empty
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,235 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'thor/base'
3
+
4
+ class Amazing
5
+ desc "hello", "say hello"
6
+ def hello
7
+ puts "Hello"
8
+ end
9
+ end
10
+
11
+ describe Thor::Base do
12
+ describe "#initialize" do
13
+ it "sets arguments array" do
14
+ base = MyCounter.new [1, 2]
15
+ base.first.must == 1
16
+ base.second.must == 2
17
+ end
18
+
19
+ it "sets arguments default values" do
20
+ base = MyCounter.new [1]
21
+ base.second.must == 2
22
+ end
23
+
24
+ it "sets options default values" do
25
+ base = MyCounter.new [1, 2]
26
+ base.options[:third].must == 3
27
+ end
28
+
29
+ it "allows options to be given as symbols or strings" do
30
+ base = MyCounter.new [1, 2], :third => 4
31
+ base.options[:third].must == 4
32
+
33
+ base = MyCounter.new [1, 2], "third" => 4
34
+ base.options[:third].must == 4
35
+ end
36
+
37
+ it "creates options with indifferent access" do
38
+ base = MyCounter.new [1, 2], :third => 3
39
+ base.options['third'].must == 3
40
+ end
41
+
42
+ it "creates options with magic predicates" do
43
+ base = MyCounter.new [1, 2], :third => 3
44
+ base.options.third.must == 3
45
+ end
46
+ end
47
+
48
+ describe "#no_tasks" do
49
+ it "avoids methods being added as tasks" do
50
+ MyScript.tasks.keys.must include("animal")
51
+ MyScript.tasks.keys.must_not include("this_is_not_a_task")
52
+ end
53
+ end
54
+
55
+ describe "#argument" do
56
+ it "sets a value as required and creates an accessor for it" do
57
+ MyCounter.start(["1", "2", "--third", "3"])[0].must == 1
58
+ Scripts::MyScript.start(["zoo", "my_special_param", "--param=normal_param"]).must == "my_special_param"
59
+ end
60
+
61
+ it "does not set a value in the options hash" do
62
+ BrokenCounter.start(["1", "2", "--third", "3"])[0].must be_nil
63
+ end
64
+ end
65
+
66
+ describe "#arguments" do
67
+ it "returns the arguments for the class" do
68
+ MyCounter.arguments.must have(2).items
69
+ end
70
+ end
71
+
72
+ describe "#class_option" do
73
+ it "sets options class wise" do
74
+ MyCounter.start(["1", "2", "--third", "3"])[2].must == 3
75
+ end
76
+
77
+ it "does not create an acessor for it" do
78
+ BrokenCounter.start(["1", "2", "--third", "3"])[3].must be_false
79
+ end
80
+ end
81
+
82
+ describe "#class_options" do
83
+ it "sets default options overwriting superclass definitions" do
84
+ options = Scripts::MyScript.class_options
85
+ options[:force].must_not be_required
86
+ end
87
+ end
88
+
89
+ describe "#remove_argument" do
90
+ it "removes previous defined arguments from class" do
91
+ ClearCounter.arguments.must be_empty
92
+ end
93
+
94
+ it "undefine accessors if required" do
95
+ ClearCounter.new.must_not respond_to(:first)
96
+ ClearCounter.new.must_not respond_to(:second)
97
+ end
98
+ end
99
+
100
+ describe "#remove_class_option" do
101
+ it "removes previous defined class option" do
102
+ ClearCounter.class_options[:third].must be_nil
103
+ end
104
+ end
105
+
106
+ describe "#class_options_help" do
107
+ before(:each) do
108
+ @content = capture(:stdout) { MyCounter.help(Thor::Base.shell.new) }
109
+ end
110
+
111
+ it "shows options description" do
112
+ @content.must =~ /# The third argument/
113
+ end
114
+
115
+ it "shows usage with banner content" do
116
+ @content.must =~ /\[\-\-third=THREE\]/
117
+ end
118
+
119
+ it "shows default values below description" do
120
+ @content.must =~ /# Default: 3/
121
+ end
122
+
123
+ it "shows options in different groups" do
124
+ @content.must =~ /Options\:/
125
+ @content.must =~ /Runtime options\:/
126
+ @content.must =~ /\-p, \[\-\-pretend\]/
127
+ end
128
+
129
+ it "use padding in options that does not have aliases" do
130
+ @content.must =~ /^ -t, \[--third/
131
+ @content.must =~ /^ \[--fourth/
132
+ end
133
+
134
+ it "allows extra options to be given" do
135
+ hash = { "Foo" => B.class_options.values }
136
+
137
+ content = capture(:stdout) { MyCounter.send(:class_options_help, Thor::Base.shell.new, nil, hash) }
138
+ content.must =~ /Foo options\:/
139
+ content.must =~ /--last-name=LAST_NAME/
140
+ end
141
+ end
142
+
143
+ describe "#namespace" do
144
+ it "returns the default class namespace" do
145
+ Scripts::MyScript.namespace.must == "scripts:my_script"
146
+ end
147
+
148
+ it "sets a namespace to the class" do
149
+ Scripts::MyDefaults.namespace.must == "default"
150
+ end
151
+ end
152
+
153
+ describe "#group" do
154
+ it "sets a group" do
155
+ MyScript.group.must == "script"
156
+ end
157
+
158
+ it "inherits the group from parent" do
159
+ MyChildScript.group.must == "script"
160
+ end
161
+
162
+ it "defaults to standard if no group is given" do
163
+ Amazing.group.must == "standard"
164
+ end
165
+ end
166
+
167
+ describe "#subclasses" do
168
+ it "tracks its subclasses in an Array" do
169
+ Thor::Base.subclasses.must include(MyScript)
170
+ Thor::Base.subclasses.must include(MyChildScript)
171
+ Thor::Base.subclasses.must include(Scripts::MyScript)
172
+ end
173
+ end
174
+
175
+ describe "#subclass_files" do
176
+ it "returns tracked subclasses, grouped by the files they come from" do
177
+ thorfile = File.join(File.dirname(__FILE__), "fixtures", "script.thor")
178
+ Thor::Base.subclass_files[File.expand_path(thorfile)].must == [
179
+ MyScript, MyScript::AnotherScript, MyChildScript, Scripts::MyScript, Scripts::MyDefaults
180
+ ]
181
+ end
182
+
183
+ it "tracks a single subclass across multiple files" do
184
+ thorfile = File.join(File.dirname(__FILE__), "fixtures", "task.thor")
185
+ Thor::Base.subclass_files[File.expand_path(thorfile)].must include(Amazing)
186
+ Thor::Base.subclass_files[File.expand_path(__FILE__)].must include(Amazing)
187
+ end
188
+ end
189
+
190
+ describe "#tasks" do
191
+ it "returns a list with all tasks defined in this class" do
192
+ MyChildScript.new.must respond_to("animal")
193
+ MyChildScript.tasks.keys.must include("animal")
194
+ end
195
+
196
+ it "raises an error if a task with reserved word is defined" do
197
+ lambda {
198
+ klass = Class.new(Thor::Group)
199
+ klass.class_eval "def shell; end"
200
+ }.must raise_error(RuntimeError, /"shell" is a Thor reserved word and cannot be defined as task/)
201
+ end
202
+ end
203
+
204
+ describe "#all_tasks" do
205
+ it "returns a list with all tasks defined in this class plus superclasses" do
206
+ MyChildScript.new.must respond_to("foo")
207
+ MyChildScript.all_tasks.keys.must include("foo")
208
+ end
209
+ end
210
+
211
+ describe "#remove_task" do
212
+ it "removes the task from its tasks hash" do
213
+ MyChildScript.tasks.keys.must_not include("bar")
214
+ MyChildScript.tasks.keys.must_not include("boom")
215
+ end
216
+
217
+ it "undefines the method if desired" do
218
+ MyChildScript.new.must_not respond_to("boom")
219
+ end
220
+ end
221
+
222
+ describe "#from_superclass" do
223
+ it "does not send a method to the superclass if the superclass does not respond to it" do
224
+ MyCounter.get_from_super.must == 13
225
+ end
226
+ end
227
+
228
+ describe "#start" do
229
+ it "raises an error instead of rescueing if --debug is given" do
230
+ lambda {
231
+ MyScript.start ["what", "--debug"]
232
+ }.must raise_error(Thor::UndefinedTaskError, /the 'what' task of MyScript is private/)
233
+ end
234
+ end
235
+ end