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,43 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'thor/core_ext/hash_with_indifferent_access'
3
+
4
+ describe Thor::CoreExt::HashWithIndifferentAccess do
5
+ before(:each) do
6
+ @hash = Thor::CoreExt::HashWithIndifferentAccess.new :foo => 'bar', 'baz' => 'bee', :force => true
7
+ end
8
+
9
+ it "has values accessible by either strings or symbols" do
10
+ @hash['foo'].must == 'bar'
11
+ @hash[:foo].must == 'bar'
12
+
13
+ @hash.values_at(:foo, :baz).must == ['bar', 'bee']
14
+ @hash.delete(:foo).must == 'bar'
15
+ end
16
+
17
+ it "handles magic boolean predicates" do
18
+ @hash.force?.must be_true
19
+ @hash.foo?.must be_true
20
+ @hash.nothing?.must be_false
21
+ end
22
+
23
+ it "handles magic comparisions" do
24
+ @hash.foo?('bar').must be_true
25
+ @hash.foo?('bee').must be_false
26
+ end
27
+
28
+ it "maps methods to keys" do
29
+ @hash.foo.must == @hash['foo']
30
+ end
31
+
32
+ it "merges keys independent if they are symbols or strings" do
33
+ @hash.merge!('force' => false, :baz => "boom")
34
+ @hash[:force].must == false
35
+ @hash[:baz].must == "boom"
36
+ end
37
+
38
+ it "creates a new hash by merging keys independent if they are symbols or strings" do
39
+ other = @hash.merge('force' => false, :baz => "boom")
40
+ other[:force].must == false
41
+ other[:baz].must == "boom"
42
+ end
43
+ end
@@ -0,0 +1,115 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require 'thor/core_ext/ordered_hash'
3
+
4
+ describe Thor::CoreExt::OrderedHash do
5
+ before :each do
6
+ @hash = Thor::CoreExt::OrderedHash.new
7
+ end
8
+
9
+ describe "without any items" do
10
+ it "returns nil for an undefined key" do
11
+ @hash["foo"].must be_nil
12
+ end
13
+
14
+ it "doesn't iterate through any items" do
15
+ @hash.each { fail }
16
+ end
17
+
18
+ it "has an empty key and values list" do
19
+ @hash.keys.must be_empty
20
+ @hash.values.must be_empty
21
+ end
22
+
23
+ it "must be empty" do
24
+ @hash.must be_empty
25
+ end
26
+ end
27
+
28
+ describe "with several items" do
29
+ before :each do
30
+ @hash[:foo] = "Foo!"
31
+ @hash[:bar] = "Bar!"
32
+ @hash[:baz] = "Baz!"
33
+ @hash[:bop] = "Bop!"
34
+ @hash[:bat] = "Bat!"
35
+ end
36
+
37
+ it "returns nil for an undefined key" do
38
+ @hash[:boom].must be_nil
39
+ end
40
+
41
+ it "returns the value for each key" do
42
+ @hash[:foo].must == "Foo!"
43
+ @hash[:bar].must == "Bar!"
44
+ @hash[:baz].must == "Baz!"
45
+ @hash[:bop].must == "Bop!"
46
+ @hash[:bat].must == "Bat!"
47
+ end
48
+
49
+ it "iterates through the keys and values in order of assignment" do
50
+ arr = []
51
+ @hash.each do |key, value|
52
+ arr << [key, value]
53
+ end
54
+ arr.must == [[:foo, "Foo!"], [:bar, "Bar!"], [:baz, "Baz!"],
55
+ [:bop, "Bop!"], [:bat, "Bat!"]]
56
+ end
57
+
58
+ it "returns the keys in order of insertion" do
59
+ @hash.keys.must == [:foo, :bar, :baz, :bop, :bat]
60
+ end
61
+
62
+ it "returns the values in order of insertion" do
63
+ @hash.values.must == ["Foo!", "Bar!", "Baz!", "Bop!", "Bat!"]
64
+ end
65
+
66
+ it "does not move an overwritten node to the end of the ordering" do
67
+ @hash[:baz] = "Bip!"
68
+ @hash.values.must == ["Foo!", "Bar!", "Bip!", "Bop!", "Bat!"]
69
+
70
+ @hash[:foo] = "Bip!"
71
+ @hash.values.must == ["Bip!", "Bar!", "Bip!", "Bop!", "Bat!"]
72
+
73
+ @hash[:bat] = "Bip!"
74
+ @hash.values.must == ["Bip!", "Bar!", "Bip!", "Bop!", "Bip!"]
75
+ end
76
+
77
+ it "appends another ordered hash while preserving ordering" do
78
+ other_hash = Thor::CoreExt::OrderedHash.new
79
+ other_hash[1] = "one"
80
+ other_hash[2] = "two"
81
+ other_hash[3] = "three"
82
+ @hash.merge(other_hash).values.must == ["Foo!", "Bar!", "Baz!", "Bop!", "Bat!", "one", "two", "three"]
83
+ end
84
+
85
+ it "overwrites hash keys with matching appended keys" do
86
+ other_hash = Thor::CoreExt::OrderedHash.new
87
+ other_hash[:bar] = "bar"
88
+ @hash.merge(other_hash)[:bar].must == "bar"
89
+ @hash[:bar].must == "Bar!"
90
+ end
91
+
92
+ it "converts to an array" do
93
+ @hash.to_a.must == [[:foo, "Foo!"], [:bar, "Bar!"], [:baz, "Baz!"], [:bop, "Bop!"], [:bat, "Bat!"]]
94
+ end
95
+
96
+ it "must not be empty" do
97
+ @hash.must_not be_empty
98
+ end
99
+
100
+ it "deletes values from hash" do
101
+ @hash.delete(:baz).must == "Baz!"
102
+ @hash.values.must == ["Foo!", "Bar!", "Bop!", "Bat!"]
103
+
104
+ @hash.delete(:foo).must == "Foo!"
105
+ @hash.values.must == ["Bar!", "Bop!", "Bat!"]
106
+
107
+ @hash.delete(:bat).must == "Bat!"
108
+ @hash.values.must == ["Bar!", "Bop!"]
109
+ end
110
+
111
+ it "returns nil if the value to be deleted can't be found" do
112
+ @hash.delete(:nothing).must be_nil
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,6 @@
1
+ class Execute < Thor
2
+ desc "ls", "Execute ls"
3
+ def ls
4
+ system "ls"
5
+ end
6
+ end
@@ -0,0 +1 @@
1
+ class <%= @klass %>; end
@@ -0,0 +1,177 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Thor::Group do
4
+ describe "#start" do
5
+ it "invokes all the tasks under the Thor group" do
6
+ MyCounter.start(["1", "2", "--third", "3"]).must == [ 1, 2, 3 ]
7
+ end
8
+
9
+ it "uses argument default value" do
10
+ MyCounter.start(["1", "--third", "3"]).must == [ 1, 2, 3 ]
11
+ end
12
+
13
+ it "invokes all the tasks in the Thor group and his parents" do
14
+ BrokenCounter.start(["1", "2", "--third", "3"]).must == [ nil, 2, 3, false, 5 ]
15
+ end
16
+
17
+ it "raises an error if a required argument is added after a non-required" do
18
+ lambda {
19
+ MyCounter.argument(:foo, :type => :string)
20
+ }.must raise_error(ArgumentError, 'You cannot have "foo" as required argument after the non-required argument "second".')
21
+ end
22
+
23
+ it "raises when an exception happens within the task call" do
24
+ lambda { BrokenCounter.start(["1", "2", "--fail"]) }.must raise_error
25
+ end
26
+
27
+ it "raises an error when a Thor group task expects arguments" do
28
+ lambda { WhinyGenerator.start }.must raise_error(ArgumentError, /Are you sure it has arity equals to 0\?/)
29
+ end
30
+
31
+ it "invokes help message if any of the shortcuts is given" do
32
+ stub(MyCounter).help
33
+ MyCounter.start(["-h"])
34
+ end
35
+ end
36
+
37
+ describe "#desc" do
38
+ it "sets the description for a given class" do
39
+ MyCounter.desc.must == "Description:\n This generator run three tasks: one, two and three.\n"
40
+ end
41
+
42
+ it "can be inherited" do
43
+ BrokenCounter.desc.must == "Description:\n This generator run three tasks: one, two and three.\n"
44
+ end
45
+
46
+ it "can be nil" do
47
+ WhinyGenerator.desc.must be_nil
48
+ end
49
+ end
50
+
51
+ describe "#help" do
52
+ before(:each) do
53
+ @content = capture(:stdout){ MyCounter.help(Thor::Base.shell.new) }
54
+ end
55
+
56
+ it "provides usage information" do
57
+ @content.must =~ /my_counter N \[N\]/
58
+ end
59
+
60
+ it "shows description" do
61
+ @content.must =~ /Description:/
62
+ @content.must =~ /This generator run three tasks: one, two and three./
63
+ end
64
+
65
+ it "shows options information" do
66
+ @content.must =~ /Options/
67
+ @content.must =~ /\[\-\-third=THREE\]/
68
+ end
69
+
70
+ it "shows only usage if a short help is required" do
71
+ content = capture(:stdout){ MyCounter.help(Thor::Base.shell.new, :short => true) }
72
+ content.must =~ /my_counter N \[N\]/
73
+ content.must_not =~ /Options/
74
+ end
75
+ end
76
+
77
+ describe "#invoke" do
78
+ before(:each) do
79
+ @content = capture(:stdout){ E.start }
80
+ end
81
+
82
+ it "allows to invoke a class from the class binding" do
83
+ @content.must =~ /1\n2\n3\n4\n5\n/
84
+ end
85
+
86
+ it "shows invocation information to the user" do
87
+ @content.must =~ /invoke Defined/
88
+ end
89
+
90
+ it "uses padding on status generated by the invoked class" do
91
+ @content.must =~ /finished counting/
92
+ end
93
+
94
+ it "allows invocation to be configured with blocks" do
95
+ capture(:stdout) do
96
+ F.start.must == ["Valim, Jose"]
97
+ end
98
+ end
99
+
100
+ it "shows invoked options on help" do
101
+ content = capture(:stdout){ E.help(Thor::Base.shell.new) }
102
+ content.must =~ /Defined options:/
103
+ content.must =~ /\[--unused\]/
104
+ content.must =~ /# This option has no use/
105
+ end
106
+ end
107
+
108
+ describe "#invoke_from_option" do
109
+ describe "with default type" do
110
+ before(:each) do
111
+ @content = capture(:stdout){ G.start }
112
+ end
113
+
114
+ it "allows to invoke a class from the class binding by a default option" do
115
+ @content.must =~ /1\n2\n3\n4\n5\n/
116
+ end
117
+
118
+ it "does not invoke if the option is nil" do
119
+ capture(:stdout){ G.start(["--skip-invoked"]) }.must_not =~ /invoke/
120
+ end
121
+
122
+ it "prints a message if invocation cannot be found" do
123
+ content = capture(:stdout){ G.start(["--invoked", "unknown"]) }
124
+ content.must =~ /error unknown \[not found\]/
125
+ end
126
+
127
+ it "allows to invoke a class from the class binding by the given option" do
128
+ content = capture(:stdout){ G.start(["--invoked", "e"]) }
129
+ content.must =~ /invoke e/
130
+ end
131
+
132
+ it "shows invocation information to the user" do
133
+ @content.must =~ /invoke defined/
134
+ end
135
+
136
+ it "uses padding on status generated by the invoked class" do
137
+ @content.must =~ /finished counting/
138
+ end
139
+
140
+ it "shows invoked options on help" do
141
+ content = capture(:stdout){ G.help(Thor::Base.shell.new) }
142
+ content.must =~ /defined options:/
143
+ content.must =~ /\[--unused\]/
144
+ content.must =~ /# This option has no use/
145
+ end
146
+ end
147
+
148
+ describe "with boolean type" do
149
+ before(:each) do
150
+ @content = capture(:stdout){ H.start }
151
+ end
152
+
153
+ it "allows to invoke a class from the class binding by a default option" do
154
+ @content.must =~ /1\n2\n3\n4\n5\n/
155
+ end
156
+
157
+ it "does not invoke if the option is false" do
158
+ capture(:stdout){ H.start(["--no-defined"]) }.must_not =~ /invoke/
159
+ end
160
+
161
+ it "shows invocation information to the user" do
162
+ @content.must =~ /invoke defined/
163
+ end
164
+
165
+ it "uses padding on status generated by the invoked class" do
166
+ @content.must =~ /finished counting/
167
+ end
168
+
169
+ it "shows invoked options on help" do
170
+ content = capture(:stdout){ H.help(Thor::Base.shell.new) }
171
+ content.must =~ /defined options:/
172
+ content.must =~ /\[--unused\]/
173
+ content.must =~ /# This option has no use/
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,107 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'thor/base'
3
+
4
+ describe Thor::Invocation do
5
+ describe "#invoke" do
6
+ it "invokes a task inside another task" do
7
+ capture(:stdout){ A.new.invoke(:two) }.must == "2\n3\n"
8
+ end
9
+
10
+ it "invokes a task just once" do
11
+ capture(:stdout){ A.new.invoke(:one) }.must == "1\n2\n3\n"
12
+ end
13
+
14
+ it "invokes a task just once even if they belongs to different classes" do
15
+ capture(:stdout){ Defined.new.invoke(:one) }.must == "1\n2\n3\n4\n5\n"
16
+ end
17
+
18
+ it "invokes a task with arguments" do
19
+ A.new.invoke(:five, [5]).must be_true
20
+ A.new.invoke(:five, [7]).must be_false
21
+ end
22
+
23
+ it "invokes the default task if none is given to a Thor class" do
24
+ content = capture(:stdout){ A.new.invoke("b") }
25
+ content.must =~ /Tasks/
26
+ content.must =~ /LAST_NAME/
27
+ end
28
+
29
+ it "accepts a class as argument without a task to invoke" do
30
+ content = capture(:stdout){ A.new.invoke(B) }
31
+ content.must =~ /Tasks/
32
+ content.must =~ /LAST_NAME/
33
+ end
34
+
35
+ it "accepts a class as argument with a task to invoke" do
36
+ base = A.new([], :last_name => "Valim")
37
+ base.invoke(B, :one, ["Jose"]).must == "Valim, Jose"
38
+ end
39
+
40
+ it "accepts a Thor instance as argument" do
41
+ invoked = B.new([], :last_name => "Valim")
42
+ base = A.new
43
+ base.invoke(invoked, :one, ["Jose"]).must == "Valim, Jose"
44
+ base.invoke(invoked, :one, ["Jose"]).must be_nil
45
+ end
46
+
47
+ it "allows customized options to be given" do
48
+ base = A.new([], :last_name => "Wrong")
49
+ base.invoke(B, :one, ["Jose"], :last_name => "Valim").must == "Valim, Jose"
50
+ end
51
+
52
+ it "reparses options in the new class" do
53
+ A.start(["invoker", "--last-name", "Valim"]).must == "Valim, Jose"
54
+ end
55
+
56
+ it "shares initialize options with invoked class" do
57
+ A.new([], :foo => :bar).invoke("b:two").must == { "foo" => :bar }
58
+ end
59
+
60
+ it "dump configuration values to be used in the invoked class" do
61
+ base = A.new
62
+ base.invoke("b:three").shell.must == base.shell
63
+ end
64
+
65
+ it "allow extra configuration values to be given" do
66
+ base, shell = A.new, Thor::Base.shell.new
67
+ base.invoke("b:three", [], {}, :shell => shell).shell.must == shell
68
+ end
69
+
70
+ it "invokes a Thor::Group and all of its tasks" do
71
+ capture(:stdout){ A.new.invoke(:c) }.must == "1\n2\n3\n"
72
+ end
73
+
74
+ it "does not invoke a Thor::Group twice" do
75
+ base = A.new
76
+ silence(:stdout){ base.invoke(:c) }
77
+ capture(:stdout){ base.invoke(:c) }.must be_empty
78
+ end
79
+
80
+ it "does not invoke any of Thor::Group tasks twice" do
81
+ base = A.new
82
+ silence(:stdout){ base.invoke(:c) }
83
+ capture(:stdout){ base.invoke("c:one") }.must be_empty
84
+ end
85
+
86
+ it "raises Thor::UndefinedTaskError if the task can't be found" do
87
+ lambda do
88
+ A.new.invoke("foo:bar")
89
+ end.must raise_error(Thor::UndefinedTaskError)
90
+ end
91
+
92
+ it "raises Thor::UndefinedTaskError if the task can't be found even if all tasks where already executed" do
93
+ base = C.new
94
+ silence(:stdout){ base.invoke }
95
+
96
+ lambda do
97
+ base.invoke("foo:bar")
98
+ end.must raise_error(Thor::UndefinedTaskError)
99
+ end
100
+
101
+ it "raises an error if a non Thor class is given" do
102
+ lambda do
103
+ A.new.invoke(Object)
104
+ end.must raise_error(RuntimeError, "Expected Thor class, got Object")
105
+ end
106
+ end
107
+ end