thor 0.9.9 → 0.11.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGELOG.rdoc +29 -4
  2. data/README.rdoc +234 -0
  3. data/Thorfile +57 -0
  4. data/VERSION +1 -0
  5. data/bin/rake2thor +4 -0
  6. data/bin/thor +1 -1
  7. data/lib/thor.rb +216 -119
  8. data/lib/thor/actions.rb +272 -0
  9. data/lib/thor/actions/create_file.rb +102 -0
  10. data/lib/thor/actions/directory.rb +87 -0
  11. data/lib/thor/actions/empty_directory.rb +133 -0
  12. data/lib/thor/actions/file_manipulation.rb +195 -0
  13. data/lib/thor/actions/inject_into_file.rb +78 -0
  14. data/lib/thor/base.rb +510 -0
  15. data/lib/thor/core_ext/hash_with_indifferent_access.rb +75 -0
  16. data/lib/thor/core_ext/ordered_hash.rb +100 -0
  17. data/lib/thor/error.rb +25 -1
  18. data/lib/thor/group.rb +263 -0
  19. data/lib/thor/invocation.rb +178 -0
  20. data/lib/thor/parser.rb +4 -0
  21. data/lib/thor/parser/argument.rb +67 -0
  22. data/lib/thor/parser/arguments.rb +145 -0
  23. data/lib/thor/parser/option.rb +132 -0
  24. data/lib/thor/parser/options.rb +142 -0
  25. data/lib/thor/rake_compat.rb +67 -0
  26. data/lib/thor/runner.rb +232 -242
  27. data/lib/thor/shell.rb +72 -0
  28. data/lib/thor/shell/basic.rb +220 -0
  29. data/lib/thor/shell/color.rb +108 -0
  30. data/lib/thor/task.rb +97 -60
  31. data/lib/thor/util.rb +230 -55
  32. data/spec/actions/create_file_spec.rb +170 -0
  33. data/spec/actions/directory_spec.rb +118 -0
  34. data/spec/actions/empty_directory_spec.rb +91 -0
  35. data/spec/actions/file_manipulation_spec.rb +242 -0
  36. data/spec/actions/inject_into_file_spec.rb +80 -0
  37. data/spec/actions_spec.rb +291 -0
  38. data/spec/base_spec.rb +236 -0
  39. data/spec/core_ext/hash_with_indifferent_access_spec.rb +43 -0
  40. data/spec/core_ext/ordered_hash_spec.rb +115 -0
  41. data/spec/fixtures/bundle/execute.rb +6 -0
  42. data/spec/fixtures/doc/config.rb +1 -0
  43. data/spec/group_spec.rb +177 -0
  44. data/spec/invocation_spec.rb +107 -0
  45. data/spec/parser/argument_spec.rb +47 -0
  46. data/spec/parser/arguments_spec.rb +64 -0
  47. data/spec/parser/option_spec.rb +212 -0
  48. data/spec/parser/options_spec.rb +255 -0
  49. data/spec/rake_compat_spec.rb +64 -0
  50. data/spec/runner_spec.rb +204 -0
  51. data/spec/shell/basic_spec.rb +206 -0
  52. data/spec/shell/color_spec.rb +41 -0
  53. data/spec/shell_spec.rb +25 -0
  54. data/spec/spec_helper.rb +52 -0
  55. data/spec/task_spec.rb +82 -0
  56. data/spec/thor_spec.rb +234 -0
  57. data/spec/util_spec.rb +196 -0
  58. metadata +69 -25
  59. data/README.markdown +0 -76
  60. data/Rakefile +0 -6
  61. data/lib/thor/options.rb +0 -242
  62. data/lib/thor/ordered_hash.rb +0 -64
  63. data/lib/thor/task_hash.rb +0 -22
  64. data/lib/thor/tasks.rb +0 -77
  65. data/lib/thor/tasks/package.rb +0 -18
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Thor::Shell::Color do
4
+ def shell
5
+ @shell ||= Thor::Shell::Color.new
6
+ end
7
+
8
+ describe "#say" do
9
+ it "set the color if specified" do
10
+ mock($stdout).puts("\e[32mWow! Now we have colors!\e[0m")
11
+ shell.say "Wow! Now we have colors!", :green
12
+ end
13
+
14
+ it "does not use a new line even with colors" do
15
+ mock($stdout).print("\e[32mWow! Now we have colors! \e[0m")
16
+ shell.say "Wow! Now we have colors! ", :green
17
+ end
18
+ end
19
+
20
+ describe "#say_status" do
21
+ it "uses color to say status" do
22
+ mock($stdout).puts("\e[1m\e[31m conflict\e[0m README")
23
+ shell.say_status :conflict, "README", :red
24
+ end
25
+ end
26
+
27
+ describe "#file_collision" do
28
+ describe "when a block is given" do
29
+ it "invokes the diff command" do
30
+ stub($stdout).print
31
+ mock($stdin).gets{ 'd' }
32
+ mock($stdin).gets{ 'n' }
33
+
34
+ output = capture(:stdout){ shell.file_collision('spec/fixtures/doc/README'){ "README\nEND\n" } }
35
+ output.must =~ /\e\[31m\- __start__\e\[0m/
36
+ output.must =~ /^ README/
37
+ output.must =~ /\e\[32m\+ END\e\[0m/
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Thor::Shell do
4
+ def shell
5
+ @shell ||= Thor::Base.shell.new
6
+ end
7
+
8
+ describe "#initialize" do
9
+ it "sets shell value" do
10
+ base = MyCounter.new [1, 2], { }, :shell => shell
11
+ base.shell.must == shell
12
+ end
13
+
14
+ it "sets the base value on the shell if an accessor is available" do
15
+ base = MyCounter.new [1, 2], { }, :shell => shell
16
+ shell.base.must == base
17
+ end
18
+ end
19
+
20
+ describe "#shell" do
21
+ it "returns the shell in use" do
22
+ MyCounter.new([1,2]).shell.must be_kind_of(Thor::Base.shell)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,52 @@
1
+ $TESTING=true
2
+
3
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'thor'
6
+ require 'stringio'
7
+ require 'rubygems'
8
+ require 'rr'
9
+ require 'diff/lcs' # You need diff/lcs installed to run specs (but not to run Thor).
10
+
11
+ # Load fixtures
12
+ load File.join(File.dirname(__FILE__), "fixtures", "task.thor")
13
+ load File.join(File.dirname(__FILE__), "fixtures", "group.thor")
14
+ load File.join(File.dirname(__FILE__), "fixtures", "script.thor")
15
+ load File.join(File.dirname(__FILE__), "fixtures", "invoke.thor")
16
+
17
+ # Set shell to basic
18
+ Thor::Base.shell = Thor::Shell::Basic
19
+
20
+ Kernel.module_eval do
21
+ alias_method :must, :should
22
+ alias_method :must_not, :should_not
23
+ undef_method :should
24
+ undef_method :should_not
25
+ end
26
+
27
+ Spec::Runner.configure do |config|
28
+ config.mock_with :rr
29
+
30
+ def capture(stream)
31
+ begin
32
+ stream = stream.to_s
33
+ eval "$#{stream} = StringIO.new"
34
+ yield
35
+ result = eval("$#{stream}").string
36
+ ensure
37
+ eval("$#{stream} = #{stream.upcase}")
38
+ end
39
+
40
+ result
41
+ end
42
+
43
+ def source_root
44
+ File.join(File.dirname(__FILE__), 'fixtures')
45
+ end
46
+
47
+ def destination_root
48
+ File.join(File.dirname(__FILE__), 'sandbox')
49
+ end
50
+
51
+ alias silence capture
52
+ end
data/spec/task_spec.rb ADDED
@@ -0,0 +1,82 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Thor::Task do
4
+ def task(options={})
5
+ options.each do |key, value|
6
+ options[key] = Thor::Option.parse(key, value)
7
+ end
8
+
9
+ @task ||= Thor::Task.new(:can_has, "I can has cheezburger", "can_has", options)
10
+ end
11
+
12
+ describe "#formatted_usage" do
13
+ it "shows usage with options" do
14
+ task('foo' => true, :bar => :required).formatted_usage.must == "can_has --bar=BAR [--foo]"
15
+ end
16
+
17
+ it "includes namespace within usage" do
18
+ stub(Object).namespace{ "foo" }
19
+ stub(Object).arguments{ [] }
20
+ task(:bar => :required).formatted_usage(Object, true).must == "foo:can_has --bar=BAR"
21
+ end
22
+
23
+ it "does not show options if required" do
24
+ stub(Object).namespace{ "foo" }
25
+ stub(Object).arguments{ [] }
26
+ task(:bar => :required).formatted_usage(Object, true, false).must == "foo:can_has"
27
+ end
28
+
29
+ it "removes default from namespace" do
30
+ stub(Object).namespace{ "default:foo" }
31
+ stub(Object).arguments{ [] }
32
+ task(:bar => :required).formatted_usage(Object, true).must == ":foo:can_has --bar=BAR"
33
+ end
34
+
35
+ it "injects arguments into usage" do
36
+ stub(Object).namespace{ "foo" }
37
+ stub(Object).arguments{ [ Thor::Argument.new(:bar, nil, true, :string) ] }
38
+ task(:foo => true).formatted_usage(Object).must == "can_has BAR [--foo]"
39
+ end
40
+ end
41
+
42
+ describe "#dynamic" do
43
+ it "creates a dynamic task with the given name" do
44
+ Thor::Task.dynamic('task').name.must == 'task'
45
+ Thor::Task.dynamic('task').description.must == 'A dynamically-generated task'
46
+ Thor::Task.dynamic('task').usage.must == 'task'
47
+ Thor::Task.dynamic('task').options.must == {}
48
+ end
49
+ end
50
+
51
+ describe "#dup" do
52
+ it "dup options hash" do
53
+ task = Thor::Task.new("can_has", nil, nil, :foo => true, :bar => :required)
54
+ task.dup.options.delete(:foo)
55
+ task.options[:foo].must_not be_nil
56
+ end
57
+ end
58
+
59
+ describe "#run" do
60
+ it "runs a task by calling a method in the given instance" do
61
+ mock = mock!.send("can_has", 1, 2, 3).subject
62
+ task.run(mock, [1, 2, 3])
63
+ end
64
+
65
+ it "raises an error if the method to be invoked is private" do
66
+ mock = mock!.private_methods{ [ 'can_has' ] }.subject
67
+ lambda {
68
+ task.run(mock)
69
+ }.must raise_error(Thor::UndefinedTaskError, "the 'can_has' task of Object is private")
70
+ end
71
+ end
72
+
73
+ describe "#short_description" do
74
+ it "returns the first line of the description" do
75
+ Thor::Task.new(:task, "I can has\ncheezburger", "can_has").short_description == "I can has"
76
+ end
77
+
78
+ it "returns the whole description if it's one line" do
79
+ Thor::Task.new(:task, "I can has cheezburger", "can_has").short_description == "I can has cheezburger"
80
+ end
81
+ end
82
+ end
data/spec/thor_spec.rb ADDED
@@ -0,0 +1,234 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Thor do
4
+ describe "#method_option" do
5
+ it "sets options to the next method to be invoked" do
6
+ args = ["foo", "bar", "--force"]
7
+ arg, options = MyScript.start(args)
8
+ options.must == { "force" => true }
9
+ end
10
+
11
+ describe "when :for is supplied" do
12
+ it "updates an already defined task" do
13
+ args, options = MyChildScript.start(["animal", "horse", "--other=fish"])
14
+ options[:other].must == "fish"
15
+ end
16
+
17
+ describe "and the target is on the parent class" do
18
+ it "updates an already defined task" do
19
+ args = ["example_default_task", "my_param", "--new-option=verified"]
20
+ options = Scripts::MyScript.start(args)
21
+ options[:new_option].must == "verified"
22
+ end
23
+
24
+ it "adds a task to the tasks list if the updated task is on the parent class" do
25
+ Scripts::MyScript.tasks["example_default_task"].must_not be_nil
26
+ end
27
+
28
+ it "clones the parent task" do
29
+ Scripts::MyScript.tasks["example_default_task"].must_not == MyChildScript.tasks["example_default_task"]
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ describe "#default_task" do
36
+ it "sets a default task" do
37
+ MyScript.default_task.must == "example_default_task"
38
+ end
39
+
40
+ it "invokes the default task if no command is specified" do
41
+ MyScript.start([]).must == "default task"
42
+ end
43
+
44
+ it "inherits the default task from parent" do
45
+ MyChildScript.default_task.must == "example_default_task"
46
+ end
47
+ end
48
+
49
+ describe "#map" do
50
+ it "calls the alias of a method if one is provided" do
51
+ MyScript.start(["-T", "fish"]).must == ["fish"]
52
+ end
53
+
54
+ it "calls the alias of a method if several are provided via .map" do
55
+ MyScript.start(["-f", "fish"]).must == ["fish", {}]
56
+ MyScript.start(["--foo", "fish"]).must == ["fish", {}]
57
+ end
58
+
59
+ it "inherits all mappings from parent" do
60
+ MyChildScript.default_task.must == "example_default_task"
61
+ end
62
+ end
63
+
64
+ describe "#desc" do
65
+ before(:all) do
66
+ @content = capture(:stdout) { MyScript.start(["help"]) }
67
+ end
68
+
69
+ it "provides description for a task" do
70
+ @content.must =~ /zoo\s+# zoo around/m
71
+ end
72
+
73
+ describe "when :for is supplied" do
74
+ it "overwrites a previous defined task" do
75
+ capture(:stdout) { MyChildScript.start(["help"]) }.must =~ /animal KIND \[\-\-other=OTHER\]\s+# fish around/m
76
+ end
77
+ end
78
+ end
79
+
80
+ describe "#method_options" do
81
+ it "sets default options if called before an initializer" do
82
+ options = MyChildScript.class_options
83
+ options[:force].type.must == :boolean
84
+ options[:param].type.must == :numeric
85
+ end
86
+
87
+ it "overwrites default options if called on the method scope" do
88
+ args = ["zoo", "--force", "--param", "feathers"]
89
+ options = MyChildScript.start(args)
90
+ options.must == { "force" => true, "param" => "feathers" }
91
+ end
92
+
93
+ it "allows default options to be merged with method options" do
94
+ args = ["animal", "bird", "--force", "--param", "1.0", "--other", "tweets"]
95
+ arg, options = MyChildScript.start(args)
96
+ arg.must == 'bird'
97
+ options.must == { "force"=>true, "param"=>1.0, "other"=>"tweets" }
98
+ end
99
+ end
100
+
101
+ describe "#start" do
102
+ it "calls a no-param method when no params are passed" do
103
+ MyScript.start(["zoo"]).must == true
104
+ end
105
+
106
+ it "calls a single-param method when a single param is passed" do
107
+ MyScript.start(["animal", "fish"]).must == ["fish"]
108
+ end
109
+
110
+ it "does not set options in attributes" do
111
+ MyScript.start(["with_optional", "--all"]).must == [nil, { "all" => true }]
112
+ end
113
+
114
+ it "raises an error if a required param is not provided" do
115
+ capture(:stderr) { MyScript.start(["animal"]) }.must =~ /'animal' was called incorrectly\. Call as 'my_script:animal TYPE'/
116
+ end
117
+
118
+ it "raises an error if the invoked task does not exist" do
119
+ capture(:stderr) { Amazing.start(["animal"]) }.must =~ /The amazing namespace doesn't have a 'animal' task/
120
+ end
121
+
122
+ it "calls method_missing if an unknown method is passed in" do
123
+ MyScript.start(["unk", "hello"]).must == [:unk, ["hello"]]
124
+ end
125
+
126
+ it "does not call a private method no matter what" do
127
+ capture(:stderr) { MyScript.start(["what"]) }.must =~ /the 'what' task of MyScript is private/
128
+ end
129
+
130
+ it "uses task default options" do
131
+ options = MyChildScript.start(["animal", "fish"]).last
132
+ options.must == { "other" => "method default" }
133
+ end
134
+
135
+ it "raises when an exception happens within the task call" do
136
+ lambda { MyScript.start(["call_myself_with_wrong_arity"]) }.must raise_error
137
+ end
138
+ end
139
+
140
+ describe "#help" do
141
+ def shell
142
+ @shell ||= Thor::Base.shell.new
143
+ end
144
+
145
+ describe "on general" do
146
+ before(:each) do
147
+ @content = capture(:stdout){ MyScript.help(shell) }
148
+ end
149
+
150
+ it "provides useful help info for the help method itself" do
151
+ @content.must =~ /help \[TASK\]\s+# Describe available tasks/m
152
+ end
153
+
154
+ it "provides useful help info for a method with params" do
155
+ @content.must =~ /animal TYPE\s+# horse around/m
156
+ end
157
+
158
+ it "provides description for tasks from classes in the same namespace" do
159
+ @content.must =~ /baz\s+# do some bazing/m
160
+ end
161
+
162
+ it "shows superclass tasks" do
163
+ content = capture(:stdout){ MyChildScript.help(shell) }
164
+ content.must =~ /foo BAR \[\-\-force\]\s+# do some fooing/m
165
+ end
166
+
167
+ it "shows class options information" do
168
+ content = capture(:stdout){ MyChildScript.help(shell) }
169
+ content.must =~ /Class options\:/
170
+ content.must =~ /\[\-\-param=N\]/
171
+ end
172
+
173
+ it "injects class arguments into default usage" do
174
+ content = capture(:stdout){ Scripts::MyScript.help(shell) }
175
+ content.must =~ /zoo ACCESSOR \-\-param\=PARAM/
176
+ end
177
+ end
178
+
179
+ describe "for a specific task" do
180
+ it "provides full help info when talking about a specific task" do
181
+ capture(:stdout) { MyScript.help(shell, "foo") }.must == <<END
182
+ Usage:
183
+ foo BAR
184
+
185
+ Method options:
186
+ [--force] # Force to do some fooing
187
+
188
+ do some fooing
189
+ This is more info!
190
+ Everyone likes more info!
191
+ END
192
+ end
193
+
194
+ it "raises an error if the task can't be found" do
195
+ lambda {
196
+ MyScript.help(shell, "unknown")
197
+ }.must raise_error(Thor::Error, "task 'unknown' could not be found in namespace 'my_script'")
198
+ end
199
+ end
200
+
201
+ describe "options" do
202
+ it "shows the namespace if required" do
203
+ capture(:stdout){ MyScript.help(shell, nil, :namespace => true) }.must =~ /my_script:foo BAR/
204
+ end
205
+
206
+ it "does not show superclass tasks if required" do
207
+ capture(:stdout){ MyScript.help(shell, nil, :short => true) }.must_not =~ /help/
208
+ end
209
+ end
210
+
211
+ describe "instance method" do
212
+ it "calls the class method" do
213
+ stub(MyScript).help(mock.instance_of(Thor::Base.shell), nil, :namespace => nil)
214
+ MyScript.start(["help"])
215
+ end
216
+ end
217
+ end
218
+
219
+ describe "when creating tasks" do
220
+ it "prints a warning if a public method is created without description or usage" do
221
+ capture(:stdout) {
222
+ klass = Class.new(Thor)
223
+ klass.class_eval "def hello_from_thor; end"
224
+ }.must =~ /\[WARNING\] Attempted to create task "hello_from_thor" without usage or description/
225
+ end
226
+
227
+ it "does not print if overwriting a previous task" do
228
+ capture(:stdout) {
229
+ klass = Class.new(Thor)
230
+ klass.class_eval "def help; end"
231
+ }.must be_empty
232
+ end
233
+ end
234
+ end
data/spec/util_spec.rb ADDED
@@ -0,0 +1,196 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ module Thor::Util
4
+ def self.clear_user_home!
5
+ @@user_home = nil
6
+ end
7
+ end
8
+
9
+ describe Thor::Util do
10
+ describe "#find_by_namespace" do
11
+ it "returns 'default' if no namespace is given" do
12
+ Thor::Util.find_by_namespace('').must == Scripts::MyDefaults
13
+ end
14
+
15
+ it "adds 'default' if namespace starts with :" do
16
+ Thor::Util.find_by_namespace(':child').must == Scripts::ChildDefault
17
+ end
18
+
19
+ it "returns nil if the namespace can't be found" do
20
+ Thor::Util.find_by_namespace('thor:core_ext:ordered_hash').must be_nil
21
+ end
22
+
23
+ it "returns a class if it matches the namespace" do
24
+ Thor::Util.find_by_namespace('app:broken:counter').must == BrokenCounter
25
+ end
26
+
27
+ it "matches classes default namespace" do
28
+ Thor::Util.find_by_namespace('scripts:my_script').must == Scripts::MyScript
29
+ end
30
+ end
31
+
32
+ describe "#namespace_from_thor_class" do
33
+ it "replaces constant nesting with task namespacing" do
34
+ Thor::Util.namespace_from_thor_class("Foo::Bar::Baz").must == "foo:bar:baz"
35
+ end
36
+
37
+ it "snake-cases component strings" do
38
+ Thor::Util.namespace_from_thor_class("FooBar::BarBaz::BazBoom").must == "foo_bar:bar_baz:baz_boom"
39
+ end
40
+
41
+ it "gets rid of an initial Default module" do
42
+ Thor::Util.namespace_from_thor_class("Default::Foo::Bar").must == ":foo:bar"
43
+ Thor::Util.namespace_from_thor_class("Default").must == ""
44
+ end
45
+
46
+ it "accepts class and module objects" do
47
+ Thor::Util.namespace_from_thor_class(Thor::CoreExt::OrderedHash).must == "thor:core_ext:ordered_hash"
48
+ Thor::Util.namespace_from_thor_class(Thor::Util).must == "thor:util"
49
+ end
50
+
51
+ it "removes Thor::Sandbox namespace" do
52
+ Thor::Util.namespace_from_thor_class("Thor::Sandbox::Package").must == "package"
53
+ end
54
+ end
55
+
56
+ describe "#namespaces_in_content" do
57
+ it "returns an array of names of constants defined in the string" do
58
+ list = Thor::Util.namespaces_in_content("class Foo; class Bar < Thor; end; end; class Baz; class Bat; end; end")
59
+ list.must include("foo:bar")
60
+ list.must_not include("bar:bat")
61
+ end
62
+
63
+ it "doesn't put the newly-defined constants in the enclosing namespace" do
64
+ Thor::Util.namespaces_in_content("class Blat; end")
65
+ defined?(Blat).must_not be
66
+ defined?(Thor::Sandbox::Blat).must be
67
+ end
68
+ end
69
+
70
+ describe "#snake_case" do
71
+ it "preserves no-cap strings" do
72
+ Thor::Util.snake_case("foo").must == "foo"
73
+ Thor::Util.snake_case("foo_bar").must == "foo_bar"
74
+ end
75
+
76
+ it "downcases all-caps strings" do
77
+ Thor::Util.snake_case("FOO").must == "foo"
78
+ Thor::Util.snake_case("FOO_BAR").must == "foo_bar"
79
+ end
80
+
81
+ it "downcases initial-cap strings" do
82
+ Thor::Util.snake_case("Foo").must == "foo"
83
+ end
84
+
85
+ it "replaces camel-casing with underscores" do
86
+ Thor::Util.snake_case("FooBarBaz").must == "foo_bar_baz"
87
+ Thor::Util.snake_case("Foo_BarBaz").must == "foo_bar_baz"
88
+ end
89
+
90
+ it "places underscores between multiple capitals" do
91
+ Thor::Util.snake_case("ABClass").must == "a_b_class"
92
+ end
93
+ end
94
+
95
+ describe "#namespace_to_thor_class_and_task" do
96
+ it "returns a Thor::Group class if full namespace matches" do
97
+ Thor::Util.namespace_to_thor_class_and_task("my_counter").must == [MyCounter, nil]
98
+ end
99
+
100
+ it "returns a Thor class if full namespace matches" do
101
+ Thor::Util.namespace_to_thor_class_and_task("thor").must == [Thor, nil]
102
+ end
103
+
104
+ it "returns a Thor class and the task name" do
105
+ Thor::Util.namespace_to_thor_class_and_task("thor:help").must == [Thor, "help"]
106
+ end
107
+
108
+ it "fallbacks in the namespace:task look up even if a full namespace does not match" do
109
+ Thor.const_set(:Help, Module.new)
110
+ Thor::Util.namespace_to_thor_class_and_task("thor:help").must == [Thor, "help"]
111
+ Thor.send :remove_const, :Help
112
+ end
113
+
114
+ describe 'errors' do
115
+ it "raises an error if the Thor class or task can't be found" do
116
+ lambda {
117
+ Thor::Util.namespace_to_thor_class_and_task("foobar")
118
+ }.must raise_error(Thor::Error, "could not find Thor class or task 'foobar'")
119
+ end
120
+ end
121
+ end
122
+
123
+ describe "#thor_classes_in" do
124
+ it "returns thor classes inside the given class" do
125
+ Thor::Util.thor_classes_in(MyScript).must == [MyScript::AnotherScript]
126
+ Thor::Util.thor_classes_in(MyScript::AnotherScript).must be_empty
127
+ end
128
+ end
129
+
130
+ describe "#user_home" do
131
+ before(:each) do
132
+ stub(ENV)[]
133
+ Thor::Util.clear_user_home!
134
+ end
135
+
136
+ it "returns the user path if none variable is set on the environment" do
137
+ Thor::Util.user_home.must == File.expand_path("~")
138
+ end
139
+
140
+ it "returns the *unix system path if file cannot be expanded and separator does not exist" do
141
+ stub(File).expand_path("~"){ raise }
142
+ previous_value = File::ALT_SEPARATOR
143
+ capture(:stderr){ File.const_set(:ALT_SEPARATOR, false) }
144
+ Thor::Util.user_home.must == "/"
145
+ capture(:stderr){ File.const_set(:ALT_SEPARATOR, previous_value) }
146
+ end
147
+
148
+ it "returns the windows system path if file cannot be expanded and a separator exists" do
149
+ stub(File).expand_path("~"){ raise }
150
+ previous_value = File::ALT_SEPARATOR
151
+ capture(:stderr){ File.const_set(:ALT_SEPARATOR, true) }
152
+ Thor::Util.user_home.must == "C:/"
153
+ capture(:stderr){ File.const_set(:ALT_SEPARATOR, previous_value) }
154
+ end
155
+
156
+ it "returns HOME/.thor if set" do
157
+ stub(ENV)["HOME"].returns{ "/home/user/" }
158
+ Thor::Util.user_home.must == "/home/user/"
159
+ end
160
+
161
+ it "returns path with HOMEDRIVE and HOMEPATH if set" do
162
+ stub(ENV)["HOMEDRIVE"].returns{ "D:/" }
163
+ stub(ENV)["HOMEPATH"].returns{ "Documents and Settings/James" }
164
+ Thor::Util.user_home.must == "D:/Documents and Settings/James"
165
+ end
166
+
167
+ it "returns APPDATA/.thor if set" do
168
+ stub(ENV)["APPDATA"].returns{ "/home/user/" }
169
+ Thor::Util.user_home.must == "/home/user/"
170
+ end
171
+ end
172
+
173
+ describe "#convert_constants_to_namespaces" do
174
+ before(:each) do
175
+ @hash = {
176
+ :git => {
177
+ :constants => [Object, "Thor::Sandbox::Package", Thor::CoreExt::OrderedHash]
178
+ }
179
+ }
180
+ end
181
+
182
+ it "converts constants in the hash to namespaces" do
183
+ Thor::Util.convert_constants_to_namespaces(@hash)
184
+ @hash[:git][:namespaces].must == [ "object", "package", "thor:core_ext:ordered_hash" ]
185
+ end
186
+
187
+ it "returns true if the hash changed" do
188
+ Thor::Util.convert_constants_to_namespaces(@hash).must be_true
189
+ end
190
+
191
+ it "does not add namespaces to the hash if namespaces were already added" do
192
+ Thor::Util.convert_constants_to_namespaces(@hash)
193
+ Thor::Util.convert_constants_to_namespaces(@hash).must be_false
194
+ end
195
+ end
196
+ end