thor 0.9.9 → 0.11.5

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.
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