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,64 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require 'thor/rake_compat'
3
+ require 'rake/tasklib'
4
+
5
+ class RakeTask < Rake::TaskLib
6
+ def initialize
7
+ define
8
+ end
9
+
10
+ def define
11
+ desc "Say it's cool"
12
+ task :cool do
13
+ puts "COOL"
14
+ end
15
+
16
+ namespace :hiper_mega do
17
+ task :super do
18
+ puts "HIPER MEGA SUPER"
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ class ThorTask < Thor
25
+ include Thor::RakeCompat
26
+ RakeTask.new
27
+ end
28
+
29
+ describe Thor::RakeCompat do
30
+ it "adds rake tasks to thor classes too" do
31
+ task = ThorTask.tasks["cool"]
32
+ task.must be
33
+ end
34
+
35
+ it "uses rake tasks descriptions on thor" do
36
+ ThorTask.tasks["cool"].description.must == "Say it's cool"
37
+ end
38
+
39
+ it "gets usage from rake tasks name" do
40
+ ThorTask.tasks["cool"].usage.must == "cool"
41
+ end
42
+
43
+ it "uses non namespaced name as description if non is available" do
44
+ ThorTask::HiperMega.tasks["super"].description.must == "super"
45
+ end
46
+
47
+ it "converts namespaces to classes" do
48
+ ThorTask.const_get(:HiperMega).must == ThorTask::HiperMega
49
+ end
50
+
51
+ it "does not add tasks from higher namespaces in lowers namespaces" do
52
+ ThorTask.tasks["super"].must_not be
53
+ end
54
+
55
+ it "invoking the thor task invokes the rake task" do
56
+ capture(:stdout) do
57
+ ThorTask.start ["cool"]
58
+ end.must == "COOL\n"
59
+
60
+ capture(:stdout) do
61
+ ThorTask::HiperMega.start ["super"]
62
+ end.must == "HIPER MEGA SUPER\n"
63
+ end
64
+ end
@@ -0,0 +1,204 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
2
+ require 'thor/runner'
3
+
4
+ describe Thor::Runner do
5
+ describe "#help" do
6
+ it "shows information about Thor::Runner itself" do
7
+ capture(:stdout){ Thor::Runner.start(["help"]) }.must =~ /List the available thor tasks/
8
+ end
9
+
10
+ it "shows information about an specific Thor::Runner task" do
11
+ content = capture(:stdout){ Thor::Runner.start(["help", "list"]) }
12
+ content.must =~ /List the available thor tasks/
13
+ content.must_not =~ /help \[TASK\]/
14
+ end
15
+
16
+ it "shows information about a specific Thor class" do
17
+ content = capture(:stdout){ Thor::Runner.start(["help", "my_script"]) }
18
+ content.must =~ /zoo\s+# zoo around/m
19
+ end
20
+
21
+ it "shows information about an specific task from an specific Thor class" do
22
+ content = capture(:stdout){ Thor::Runner.start(["help", "my_script:zoo"]) }
23
+ content.must =~ /zoo around/
24
+ content.must_not =~ /help \[TASK\]/
25
+ end
26
+
27
+ it "shows information about a specific Thor group class" do
28
+ content = capture(:stdout){ Thor::Runner.start(["help", "my_counter"]) }
29
+ content.must =~ /my_counter N \[N\]/
30
+ end
31
+
32
+ it "raises error if a class/task cannot be found" do
33
+ content = capture(:stderr){ Thor::Runner.start(["help", "unknown"]) }
34
+ content.must =~ /could not find Thor class or task 'unknown'/
35
+ end
36
+ end
37
+
38
+ describe "#start" do
39
+ it "invokes a task from Thor::Runner" do
40
+ ARGV.replace ["list"]
41
+ capture(:stdout){ Thor::Runner.start }.must =~ /my_counter N \[N\]/
42
+ end
43
+
44
+ it "invokes a task from a specific Thor class" do
45
+ ARGV.replace ["my_script:zoo"]
46
+ Thor::Runner.start.must be_true
47
+ end
48
+
49
+ it "invokes the default task from a specific Thor class if none is specified" do
50
+ ARGV.replace ["my_script"]
51
+ Thor::Runner.start.must == "default task"
52
+ end
53
+
54
+ it "forwads arguments to the invoked task" do
55
+ ARGV.replace ["my_script:animal", "horse"]
56
+ Thor::Runner.start.must == ["horse"]
57
+ end
58
+
59
+ it "invokes tasks through shortcuts" do
60
+ ARGV.replace ["my_script", "-T", "horse"]
61
+ Thor::Runner.start.must == ["horse"]
62
+ end
63
+
64
+ it "invokes a Thor::Group" do
65
+ ARGV.replace ["my_counter", "1", "2", "--third", "3"]
66
+ Thor::Runner.start.must == [1, 2, 3]
67
+ end
68
+
69
+ it "raises an error if class/task can't be found" do
70
+ ARGV.replace ["unknown"]
71
+ capture(:stderr){ Thor::Runner.start }.must =~ /could not find Thor class or task 'unknown'/
72
+ end
73
+
74
+ it "does not swallow NoMethodErrors that occur inside the called method" do
75
+ ARGV.replace ["my_script:call_unexistent_method"]
76
+ lambda { Thor::Runner.start }.must raise_error(NoMethodError)
77
+ end
78
+
79
+ it "does not swallow Thor::Group ArgumentError" do
80
+ ARGV.replace ["whiny_generator"]
81
+ lambda { Thor::Runner.start }.must raise_error(ArgumentError, /Are you sure it has arity equals to 0\?/)
82
+ end
83
+
84
+ it "does not swallow Thor ArgumentError" do
85
+ ARGV.replace ["my_script:animal"]
86
+ capture(:stderr) { Thor::Runner.start }.must =~ /'animal' was called incorrectly\. Call as 'my_script:animal TYPE'/
87
+ end
88
+ end
89
+
90
+ describe "tasks" do
91
+ before(:each) do
92
+ @location = "#{File.dirname(__FILE__)}/fixtures/task.thor"
93
+ @original_yaml = {
94
+ "random" => {
95
+ :location => @location,
96
+ :filename => "4a33b894ffce85d7b412fc1b36f88fe0",
97
+ :constants => ["Amazing"]
98
+ }
99
+ }
100
+
101
+ # Stub load and save to avoid thor.yaml from being overwritten
102
+ stub(YAML).load_file { @original_yaml }
103
+ stub(File).exists?(File.join(Thor::Util.thor_root, "thor.yml")){ true }
104
+ stub(File).open(File.join(Thor::Util.thor_root, "thor.yml"), "w")
105
+ end
106
+
107
+ describe "list" do
108
+ it "gives a list of the available tasks" do
109
+ ARGV.replace ["list"]
110
+ content = capture(:stdout) { Thor::Runner.start }
111
+ content.must =~ /amazing:describe NAME \[\-\-forcefully\]\s+# say that someone is amazing/m
112
+ end
113
+
114
+ it "gives a list of the available Thor::Group classes" do
115
+ ARGV.replace ["list"]
116
+ capture(:stdout) { Thor::Runner.start }.must =~ /my_counter N \[N\]/
117
+ end
118
+
119
+ it "can filter a list of the available tasks by --group" do
120
+ ARGV.replace ["list", "--group", "standard"]
121
+ capture(:stdout) { Thor::Runner.start }.must =~ /amazing:describe NAME/
122
+ ARGV.replace []
123
+ capture(:stdout) { Thor::Runner.start }.must_not =~ /my_script:animal TYPE/
124
+ ARGV.replace ["list", "--group", "script"]
125
+ capture(:stdout) { Thor::Runner.start }.must =~ /my_script:animal TYPE/
126
+ end
127
+
128
+ it "can skip all filters to show all tasks using --all" do
129
+ ARGV.replace ["list", "--all"]
130
+ content = capture(:stdout) { Thor::Runner.start }
131
+ content.must =~ /amazing:describe NAME/
132
+ content.must =~ /my_script:animal TYPE/
133
+ end
134
+
135
+ it "doesn't list superclass tasks in the subclass" do
136
+ ARGV.replace ["list"]
137
+ capture(:stdout) { Thor::Runner.start }.must_not =~ /amazing:help/
138
+ end
139
+
140
+ it "presents tasks in the default namespace with an empty namespace" do
141
+ ARGV.replace ["list"]
142
+ capture(:stdout) { Thor::Runner.start }.must =~ /^:test\s+# prints 'test'/m
143
+ end
144
+
145
+ it "runs tasks with an empty namespace from the default namespace" do
146
+ ARGV.replace [":test"]
147
+ capture(:stdout) { Thor::Runner.start }.must == "test\n"
148
+ end
149
+
150
+ it "updates the yaml file when invoked" do
151
+ capture(:stdout) { Thor::Runner.start(["list"]) }
152
+ @original_yaml["random"][:namespaces].must == ["amazing"]
153
+ end
154
+ end
155
+
156
+ describe "update" do
157
+ it "updates existing thor files" do
158
+ mock.instance_of(Thor::Runner).install(@original_yaml["random"][:location]) { true }
159
+ stub(File).delete(File.join(Thor::Util.thor_root, @original_yaml["random"][:filename]))
160
+ silence(:stdout) { Thor::Runner.start(["update", "random"]) }
161
+ end
162
+ end
163
+
164
+ describe "uninstall" do
165
+ before(:each) do
166
+ stub.instance_of(Thor::Runner).save_yaml(anything)
167
+ stub(File).delete(anything)
168
+ stub(@original_yaml).delete(anything)
169
+ end
170
+
171
+ it "uninstalls existing thor modules" do
172
+ silence(:stdout) { Thor::Runner.start(["uninstall", "random"]) }
173
+ end
174
+ end
175
+
176
+ describe "installed" do
177
+ before(:each) do
178
+ stub(Dir).[](anything) { [] }
179
+ end
180
+
181
+ it "displays the modules installed in a pretty way" do
182
+ stdout = capture(:stdout) { Thor::Runner.start(["installed"]) }
183
+
184
+ stdout.must =~ /random\s*amazing/
185
+ stdout.must =~ /amazing:describe NAME \[\-\-forcefully\]\s+# say that someone is amazing/m
186
+ stdout.must =~ /amazing:hello\s+# say hello/m
187
+ end
188
+ end
189
+
190
+ describe "install" do
191
+ it "installs thor files" do
192
+ ARGV.replace ["install", @location]
193
+
194
+ # Stubs for the file system interactions
195
+ stub.instance_of(Thor::Base.shell).no? { false }
196
+ stub(FileUtils).mkdir_p
197
+ stub(FileUtils).touch
198
+
199
+ mock(File).open(File.join(Thor::Util.thor_root, Digest::MD5.hexdigest(@location + "random")), "w")
200
+ silence(:stdout) { Thor::Runner.start }
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,206 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Thor::Shell::Basic do
4
+ def shell
5
+ @shell ||= Thor::Shell::Basic.new
6
+ end
7
+
8
+ describe "#padding" do
9
+ it "cannot be set to below zero" do
10
+ shell.padding = 10
11
+ shell.padding.must == 10
12
+
13
+ shell.padding = -1
14
+ shell.padding.must == 0
15
+ end
16
+ end
17
+
18
+ describe "#ask" do
19
+ it "prints a message to the user and gets the response" do
20
+ mock($stdout).print("Should I overwrite it? ")
21
+ mock($stdin).gets{ "Sure" }
22
+ shell.ask("Should I overwrite it?").must == "Sure"
23
+ end
24
+ end
25
+
26
+ describe "#yes?" do
27
+ it "asks the user and returns true if the user replies yes" do
28
+ mock($stdout).print("Should I overwrite it? ")
29
+ mock($stdin).gets{ "y" }
30
+ shell.yes?("Should I overwrite it?").must be_true
31
+
32
+ mock($stdout).print("Should I overwrite it? ")
33
+ mock($stdin).gets{ "n" }
34
+ shell.yes?("Should I overwrite it?").must_not be_true
35
+ end
36
+ end
37
+
38
+ describe "#no?" do
39
+ it "asks the user and returns true if the user replies no" do
40
+ mock($stdout).print("Should I overwrite it? ")
41
+ mock($stdin).gets{ "n" }
42
+ shell.no?("Should I overwrite it?").must be_true
43
+
44
+ mock($stdout).print("Should I overwrite it? ")
45
+ mock($stdin).gets{ "Yes" }
46
+ shell.no?("Should I overwrite it?").must be_false
47
+ end
48
+ end
49
+
50
+ describe "#say" do
51
+ it "prints a message to the user" do
52
+ mock($stdout).puts("Running...")
53
+ shell.say("Running...")
54
+ end
55
+
56
+ it "prints a message to the user without new line if it ends with a whitespace" do
57
+ mock($stdout).print("Running... ")
58
+ shell.say("Running... ")
59
+ end
60
+ end
61
+
62
+ describe "#say_status" do
63
+ it "prints a message to the user with status" do
64
+ mock($stdout).puts(" create ~/.thor/task.thor")
65
+ shell.say_status(:create, "~/.thor/task.thor")
66
+ end
67
+
68
+ it "always use new line" do
69
+ mock($stdout).puts(" create ")
70
+ shell.say_status(:create, "")
71
+ end
72
+
73
+ it "does not print a message if base is set to quiet" do
74
+ base = MyCounter.new [1,2]
75
+ mock(base).options { Hash.new(:quiet => true) }
76
+
77
+ dont_allow($stdout).puts
78
+ shell.base = base
79
+ shell.say_status(:created, "~/.thor/task.thor")
80
+ end
81
+
82
+ it "does not print a message if log status is set to false" do
83
+ dont_allow($stdout).puts
84
+ shell.say_status(:created, "~/.thor/task.thor", false)
85
+ end
86
+
87
+ it "uses padding to set messages left margin" do
88
+ shell.padding = 2
89
+ mock($stdout).puts(" create ~/.thor/task.thor")
90
+ shell.say_status(:create, "~/.thor/task.thor")
91
+ end
92
+ end
93
+
94
+ describe "#print_list" do
95
+ before(:each) do
96
+ @list = ["abc", "#123", "first three"]
97
+ end
98
+
99
+ it "prints a list" do
100
+ content = capture(:stdout){ shell.print_list(@list) }
101
+ content.must == <<-LIST
102
+ abc
103
+ #123
104
+ first three
105
+ LIST
106
+ end
107
+
108
+ it "prints a list inline" do
109
+ content = capture(:stdout){ shell.print_list(@list, :mode => :inline) }
110
+ content.must == <<-LIST
111
+ abc, #123, and first three
112
+ LIST
113
+ end
114
+ end
115
+
116
+ describe "#print_table" do
117
+ before(:each) do
118
+ @table = []
119
+ @table << ["abc", "#123", "first three"]
120
+ @table << ["", "#0", "empty"]
121
+ @table << ["xyz", "#786", "last three"]
122
+ end
123
+
124
+ it "prints a table" do
125
+ content = capture(:stdout){ shell.print_table(@table) }
126
+ content.must == <<-TABLE
127
+ abc #123 first three
128
+ #0 empty
129
+ xyz #786 last three
130
+ TABLE
131
+ end
132
+
133
+ it "prints a table with identation" do
134
+ content = capture(:stdout){ shell.print_table(@table, :ident => 2) }
135
+ content.must == <<-TABLE
136
+ abc #123 first three
137
+ #0 empty
138
+ xyz #786 last three
139
+ TABLE
140
+ end
141
+ end
142
+
143
+ describe "#file_collision" do
144
+ it "shows a menu with options" do
145
+ mock($stdout).print('Overwrite foo? (enter "h" for help) [Ynaqh] ')
146
+ mock($stdin).gets{ 'n' }
147
+ shell.file_collision('foo')
148
+ end
149
+
150
+ it "returns false if the user choose no" do
151
+ stub($stdout).print
152
+ mock($stdin).gets{ 'n' }
153
+ shell.file_collision('foo').must be_false
154
+ end
155
+
156
+ it "returns true if the user choose yes" do
157
+ stub($stdout).print
158
+ mock($stdin).gets{ 'y' }
159
+ shell.file_collision('foo').must be_true
160
+ end
161
+
162
+ it "shows help usage if the user choose help" do
163
+ stub($stdout).print
164
+ mock($stdin).gets{ 'h' }
165
+ mock($stdin).gets{ 'n' }
166
+ help = capture(:stdout){ shell.file_collision('foo') }
167
+ help.must =~ /h \- help, show this help/
168
+ end
169
+
170
+ it "quits if the user choose quit" do
171
+ stub($stdout).print
172
+ mock($stdout).puts('Aborting...')
173
+ mock($stdin).gets{ 'q' }
174
+
175
+ lambda {
176
+ shell.file_collision('foo')
177
+ }.must raise_error(SystemExit)
178
+ end
179
+
180
+ it "always returns true if the user choose always" do
181
+ mock($stdout).print('Overwrite foo? (enter "h" for help) [Ynaqh] ')
182
+ mock($stdin).gets{ 'a' }
183
+
184
+ shell.file_collision('foo').must be_true
185
+
186
+ dont_allow($stdout).print
187
+ shell.file_collision('foo').must be_true
188
+ end
189
+
190
+ describe "when a block is given" do
191
+ it "displays diff options to the user" do
192
+ mock($stdout).print('Overwrite foo? (enter "h" for help) [Ynaqdh] ')
193
+ mock($stdin).gets{ 's' }
194
+ shell.file_collision('foo'){ }
195
+ end
196
+
197
+ it "invokes the diff command" do
198
+ stub($stdout).print
199
+ mock($stdin).gets{ 'd' }
200
+ mock($stdin).gets{ 'n' }
201
+ mock(shell).system(/diff -u/)
202
+ capture(:stdout){ shell.file_collision('foo'){ } }
203
+ end
204
+ end
205
+ end
206
+ end