thor 0.13.4 → 0.14.3

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 (45) hide show
  1. data/CHANGELOG.rdoc +5 -3
  2. data/{README.rdoc → README.md} +65 -55
  3. data/Thorfile +3 -2
  4. data/lib/thor/actions/create_file.rb +3 -1
  5. data/lib/thor/actions/directory.rb +4 -2
  6. data/lib/thor/actions/file_manipulation.rb +30 -7
  7. data/lib/thor/actions.rb +31 -9
  8. data/lib/thor/base.rb +38 -15
  9. data/lib/thor/core_ext/hash_with_indifferent_access.rb +1 -1
  10. data/lib/thor/group.rb +22 -20
  11. data/lib/thor/invocation.rb +45 -57
  12. data/lib/thor/parser/argument.rb +15 -15
  13. data/lib/thor/parser/arguments.rb +14 -3
  14. data/lib/thor/parser/option.rb +38 -46
  15. data/lib/thor/parser/options.rb +26 -22
  16. data/lib/thor/runner.rb +11 -16
  17. data/lib/thor/shell/basic.rb +48 -12
  18. data/lib/thor/shell/html.rb +121 -0
  19. data/lib/thor/shell.rb +7 -2
  20. data/lib/thor/task.rb +63 -51
  21. data/lib/thor/util.rb +15 -16
  22. data/lib/thor/version.rb +1 -1
  23. data/lib/thor.rb +118 -45
  24. data/spec/actions/directory_spec.rb +2 -2
  25. data/spec/actions/file_manipulation_spec.rb +7 -0
  26. data/spec/actions_spec.rb +21 -3
  27. data/spec/base_spec.rb +9 -3
  28. data/spec/fixtures/doc/block_helper.rb +3 -0
  29. data/spec/fixtures/doc/components/.empty_directory +0 -0
  30. data/spec/fixtures/group.thor +16 -4
  31. data/spec/fixtures/path with spaces +0 -0
  32. data/spec/fixtures/script.thor +48 -4
  33. data/spec/group_spec.rb +3 -3
  34. data/spec/invocation_spec.rb +1 -8
  35. data/spec/parser/option_spec.rb +2 -2
  36. data/spec/parser/options_spec.rb +27 -0
  37. data/spec/runner_spec.rb +16 -8
  38. data/spec/shell/basic_spec.rb +13 -4
  39. data/spec/shell/html_spec.rb +27 -0
  40. data/spec/shell_spec.rb +13 -0
  41. data/spec/spec_helper.rb +4 -3
  42. data/spec/task_spec.rb +7 -7
  43. data/spec/thor_spec.rb +103 -6
  44. data/spec/util_spec.rb +3 -7
  45. metadata +63 -7
data/lib/thor.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'thor/base'
2
2
 
3
- # TODO: Update thor to allow for git-style CLI (git bisect run)
4
3
  class Thor
5
4
  class << self
6
5
  # Sets the default task when thor is executed without an explicit task to be called.
@@ -24,6 +23,7 @@ class Thor
24
23
  # ==== Parameters
25
24
  # usage<String>
26
25
  # description<String>
26
+ # options<String>
27
27
  #
28
28
  def desc(usage, description, options={})
29
29
  if options[:for]
@@ -31,7 +31,21 @@ class Thor
31
31
  task.usage = usage if usage
32
32
  task.description = description if description
33
33
  else
34
- @usage, @desc = usage, description
34
+ @usage, @desc, @hide = usage, description, options[:hide] || false
35
+ end
36
+ end
37
+
38
+ # Defines the long description of the next task.
39
+ #
40
+ # ==== Parameters
41
+ # long description<String>
42
+ #
43
+ def long_desc(long_description, options={})
44
+ if options[:for]
45
+ task = find_and_refresh_task(options[:for])
46
+ task.long_description = long_description if long_description
47
+ else
48
+ @long_desc = long_description
35
49
  end
36
50
  end
37
51
 
@@ -112,32 +126,6 @@ class Thor
112
126
  build_option(name, options, scope)
113
127
  end
114
128
 
115
- # Parses the task and options from the given args, instantiate the class
116
- # and invoke the task. This method is used when the arguments must be parsed
117
- # from an array. If you are inside Ruby and want to use a Thor class, you
118
- # can simply initialize it:
119
- #
120
- # script = MyScript.new(args, options, config)
121
- # script.invoke(:task, first_arg, second_arg, third_arg)
122
- #
123
- def start(original_args=ARGV, config={})
124
- super do |given_args|
125
- meth = normalize_task_name(given_args.shift)
126
- task = all_tasks[meth]
127
-
128
- if task
129
- args, opts = Thor::Options.split(given_args)
130
- config.merge!(:task_options => task.options)
131
- else
132
- args, opts = given_args, {}
133
- end
134
-
135
- task ||= Thor::Task::Dynamic.new(meth)
136
- trailing = args[Range.new(arguments.size, -1)]
137
- new(args, opts, config).invoke(task, trailing || [])
138
- end
139
- end
140
-
141
129
  # Prints help information for the given task.
142
130
  #
143
131
  # ==== Parameters
@@ -153,7 +141,12 @@ class Thor
153
141
  shell.say " #{banner(task)}"
154
142
  shell.say
155
143
  class_options_help(shell, nil => task.options.map { |_, o| o })
156
- shell.say task.description
144
+ if task.long_description
145
+ shell.say "Description:"
146
+ shell.print_wrapped(task.long_description, :ident => 2)
147
+ else
148
+ shell.say task.description
149
+ end
157
150
  end
158
151
 
159
152
  # Prints help information for this class.
@@ -161,8 +154,8 @@ class Thor
161
154
  # ==== Parameters
162
155
  # shell<Thor::Shell>
163
156
  #
164
- def help(shell)
165
- list = printable_tasks
157
+ def help(shell, subcommand = false)
158
+ list = printable_tasks(true, subcommand)
166
159
  Thor::Util.thor_classes_in(self).each do |klass|
167
160
  list += klass.printable_tasks(false)
168
161
  end
@@ -175,28 +168,91 @@ class Thor
175
168
  end
176
169
 
177
170
  # Returns tasks ready to be printed.
178
- def printable_tasks(all=true)
171
+ def printable_tasks(all = true, subcommand = false)
179
172
  (all ? all_tasks : tasks).map do |_, task|
173
+ next if task.hidden?
180
174
  item = []
181
- item << banner(task)
175
+ item << banner(task, false, subcommand)
182
176
  item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
183
177
  item
178
+ end.compact
179
+ end
180
+
181
+ def subcommands
182
+ @subcommands ||= from_superclass(:subcommands, [])
183
+ end
184
+
185
+ def subcommand(subcommand, subcommand_class)
186
+ self.subcommands << subcommand.to_s
187
+ subcommand_class.subcommand_help subcommand
188
+ define_method(subcommand) { |*args| invoke subcommand_class, args }
189
+ end
190
+
191
+ # Extend check unknown options to accept a hash of conditions.
192
+ #
193
+ # === Parameters
194
+ # options<Hash>: A hash containing :only and/or :except keys
195
+ def check_unknown_options!(options={})
196
+ @check_unknown_options ||= Hash.new
197
+ options.each do |key, value|
198
+ if value
199
+ @check_unknown_options[key] = Array(value)
200
+ else
201
+ @check_unknown_options.delete(key)
202
+ end
184
203
  end
204
+ @check_unknown_options
185
205
  end
186
206
 
187
- def handle_argument_error(task, error) #:nodoc:
188
- raise InvocationError, "#{task.name.inspect} was called incorrectly. Call as #{task.formatted_usage(self, banner_base == "thor").inspect}."
207
+ # Overwrite check_unknown_options? to take subcommands and options into account.
208
+ def check_unknown_options?(config) #:nodoc:
209
+ options = check_unknown_options
210
+ return false unless options
211
+
212
+ task = config[:current_task]
213
+ return true unless task
214
+
215
+ name = task.name
216
+
217
+ if subcommands.include?(name)
218
+ false
219
+ elsif options[:except]
220
+ !options[:except].include?(name.to_sym)
221
+ elsif options[:only]
222
+ options[:only].include?(name.to_sym)
223
+ else
224
+ true
225
+ end
189
226
  end
190
227
 
191
228
  protected
192
229
 
230
+ # The method responsible for dispatching given the args.
231
+ def dispatch(meth, given_args, given_opts, config) #:nodoc:
232
+ meth ||= retrieve_task_name(given_args)
233
+ task = all_tasks[normalize_task_name(meth)]
234
+
235
+ if task
236
+ args, opts = Thor::Options.split(given_args)
237
+ else
238
+ args, opts = given_args, nil
239
+ task = Thor::DynamicTask.new(meth)
240
+ end
241
+
242
+ opts = given_opts || opts || []
243
+ config.merge!(:current_task => task, :task_options => task.options)
244
+
245
+ trailing = args[Range.new(arguments.size, -1)]
246
+ new(args, opts, config).invoke_task(task, trailing || [])
247
+ end
248
+
193
249
  # The banner for this class. You can customize it if you are invoking the
194
250
  # thor class by another ways which is not the Thor::Runner. It receives
195
251
  # the task that is going to be invoked and a boolean which indicates if
196
252
  # the namespace should be displayed as arguments.
197
253
  #
198
- def banner(task)
199
- "#{banner_base} #{task.formatted_usage(self, banner_base == "thor")}"
254
+ def banner(task, namespace = nil, subcommand = false)
255
+ "#{basename} #{task.formatted_usage(self, $thor_runner, subcommand)}"
200
256
  end
201
257
 
202
258
  def baseclass #:nodoc:
@@ -205,10 +261,11 @@ class Thor
205
261
 
206
262
  def create_task(meth) #:nodoc:
207
263
  if @usage && @desc
208
- tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options)
209
- @usage, @desc, @method_options = nil
264
+ base_class = @hide ? Thor::HiddenTask : Thor::Task
265
+ tasks[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
266
+ @usage, @desc, @long_desc, @method_options, @hide = nil
210
267
  true
211
- elsif self.all_tasks[meth.to_s] || meth.to_sym == :method_missing
268
+ elsif self.all_tasks[meth] || meth == "method_missing"
212
269
  true
213
270
  else
214
271
  puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
@@ -223,14 +280,30 @@ class Thor
223
280
  @method_options = nil
224
281
  end
225
282
 
283
+ # Retrieve the task name from given args.
284
+ def retrieve_task_name(args) #:nodoc:
285
+ meth = args.first.to_s unless args.empty?
286
+
287
+ if meth && (map[meth] || meth !~ /^\-/)
288
+ args.shift
289
+ else
290
+ nil
291
+ end
292
+ end
293
+
226
294
  # Receives a task name (can be nil), and try to get a map from it.
227
295
  # If a map can't be found use the sent name or the default task.
228
- #
229
296
  def normalize_task_name(meth) #:nodoc:
230
- mapping = map[meth.to_s]
231
- meth = mapping || meth || default_task
297
+ meth = map[meth.to_s] || meth || default_task
232
298
  meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
233
299
  end
300
+
301
+ def subcommand_help(cmd)
302
+ desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
303
+ class_eval <<-RUBY
304
+ def help(task = nil, subcommand = true); super; end
305
+ RUBY
306
+ end
234
307
  end
235
308
 
236
309
  include Thor::Base
@@ -238,7 +311,7 @@ class Thor
238
311
  map HELP_MAPPINGS => :help
239
312
 
240
313
  desc "help [TASK]", "Describe available tasks or one specific task"
241
- def help(task=nil)
242
- task ? self.class.task_help(shell, task) : self.class.help(shell)
314
+ def help(task = nil, subcommand = false)
315
+ task ? self.class.task_help(shell, task) : self.class.help(shell, subcommand)
243
316
  end
244
317
  end
@@ -37,7 +37,7 @@ describe Thor::Actions::Directory do
37
37
  it "raises an error if the source does not exist" do
38
38
  lambda {
39
39
  invoke! "unknown"
40
- }.must raise_error(Thor::Error, /Could not find "unknown" in source paths/)
40
+ }.must raise_error(Thor::Error, /Could not find "unknown" in any of your source paths/)
41
41
  end
42
42
 
43
43
  it "copies the whole directory recursively to the default destination" do
@@ -84,7 +84,7 @@ describe Thor::Actions::Directory do
84
84
  File.directory?(file).must be_true
85
85
  end
86
86
 
87
- it "does not copy .empty_diretories files" do
87
+ it "does not copy .empty_directory files" do
88
88
  invoke! "doc", "docs"
89
89
  file = File.join(destination_root, "docs", "components", ".empty_directory")
90
90
  File.exists?(file).must be_false
@@ -117,6 +117,13 @@ describe Thor::Actions do
117
117
  end
118
118
 
119
119
  describe "#template" do
120
+ it "allows using block helpers in the template" do
121
+ action :template, "doc/block_helper.rb"
122
+
123
+ file = File.join(destination_root, "doc/block_helper.rb")
124
+ File.read(file).must == "Hello world!"
125
+ end
126
+
120
127
  it "evaluates the template given as source" do
121
128
  runner.instance_variable_set("@klass", "Config")
122
129
  action :template, "doc/config.rb"
data/spec/actions_spec.rb CHANGED
@@ -107,7 +107,7 @@ describe Thor::Actions do
107
107
  it "raises an error if source path is empty" do
108
108
  lambda {
109
109
  A.new.find_in_source_paths("foo")
110
- }.must raise_error(Thor::Error, /You don't have any source path defined for class A/)
110
+ }.must raise_error(Thor::Error, /Currently you have no source paths/)
111
111
  end
112
112
 
113
113
  it "finds a template inside the source path" do
@@ -192,10 +192,28 @@ describe Thor::Actions do
192
192
  @foo = "FOO"
193
193
  say_status :cool, :padding
194
194
  TEMPLATE
195
- @template.instance_eval "def read; self; end" # Make the string respond to read
195
+ @template.stub(:read).and_return(@template)
196
196
 
197
+ @file = '/'
198
+ runner.stub(:open).and_return(@template)
199
+ end
200
+
201
+ it "accepts a URL as the path" do
197
202
  @file = "http://gist.github.com/103208.txt"
198
- runner.should_receive(:open).and_return(@template)
203
+ runner.should_receive(:open).with(@file, "Accept" => "application/x-thor-template").and_return(@template)
204
+ action(:apply, @file)
205
+ end
206
+
207
+ it "accepts a secure URL as the path" do
208
+ @file = "https://gist.github.com/103208.txt"
209
+ runner.should_receive(:open).with(@file, "Accept" => "application/x-thor-template").and_return(@template)
210
+ action(:apply, @file)
211
+ end
212
+
213
+ it "accepts a local file path with spaces" do
214
+ @file = File.expand_path("fixtures/path with spaces", File.dirname(__FILE__))
215
+ runner.should_receive(:open).with(@file).and_return(@template)
216
+ action(:apply, @file)
199
217
  end
200
218
 
201
219
  it "opens a file and executes its content in the instance binding" do
data/spec/base_spec.rb CHANGED
@@ -74,7 +74,7 @@ describe Thor::Base do
74
74
  MyCounter.start(["1", "2", "--third", "3"])[2].must == 3
75
75
  end
76
76
 
77
- it "does not create an acessor for it" do
77
+ it "does not create an accessor for it" do
78
78
  BrokenCounter.start(["1", "2", "--third", "3"])[3].must be_false
79
79
  end
80
80
  end
@@ -176,8 +176,8 @@ describe Thor::Base do
176
176
  it "returns tracked subclasses, grouped by the files they come from" do
177
177
  thorfile = File.join(File.dirname(__FILE__), "fixtures", "script.thor")
178
178
  Thor::Base.subclass_files[File.expand_path(thorfile)].must == [
179
- MyScript, MyScript::AnotherScript, MyChildScript, Scripts::MyScript,
180
- Scripts::MyDefaults, Scripts::ChildDefault
179
+ MyScript, MyScript::AnotherScript, MyChildScript, Barn,
180
+ Scripts::MyScript, Scripts::MyDefaults, Scripts::ChildDefault
181
181
  ]
182
182
  end
183
183
 
@@ -244,6 +244,12 @@ describe Thor::Base do
244
244
  MyScript.start(["foo", "bar", "--force", "true", "--unknown", "baz"])
245
245
  }.strip.must == "Unknown switches '--unknown'"
246
246
  end
247
+
248
+ it "checks unknown options except specified" do
249
+ capture(:stderr) {
250
+ MyScript.start(["with_optional", "NAME", "--omg", "--invalid"]).must == ["NAME", {}]
251
+ }.strip.must be_empty
252
+ end
247
253
  end
248
254
 
249
255
  describe "attr_*" do
@@ -0,0 +1,3 @@
1
+ <% world do -%>
2
+ Hello
3
+ <% end -%>
File without changes
@@ -6,9 +6,7 @@ class MyCounter < Thor::Group
6
6
  from_superclass(:get_from_super, 13)
7
7
  end
8
8
 
9
- def self.source_root
10
- File.expand_path(File.dirname(__FILE__))
11
- end
9
+ source_root File.expand_path(File.dirname(__FILE__))
12
10
  source_paths << File.expand_path("broken", File.dirname(__FILE__))
13
11
 
14
12
  argument :first, :type => :numeric
@@ -20,7 +18,7 @@ class MyCounter < Thor::Group
20
18
 
21
19
  desc <<-FOO
22
20
  Description:
23
- This generator run three tasks: one, two and three.
21
+ This generator runs three tasks: one, two and three.
24
22
  FOO
25
23
 
26
24
  def one
@@ -39,6 +37,13 @@ FOO
39
37
  super
40
38
  base.source_paths.unshift(File.expand_path(File.join(File.dirname(__FILE__), "doc")))
41
39
  end
40
+
41
+ no_tasks do
42
+ def world(&block)
43
+ result = capture(&block)
44
+ concat(result.strip + " world!")
45
+ end
46
+ end
42
47
  end
43
48
 
44
49
  class ClearCounter < MyCounter
@@ -81,3 +86,10 @@ class WhinyGenerator < Thor::Group
81
86
  def wrong_arity(required)
82
87
  end
83
88
  end
89
+
90
+ class TaskConflict < Thor::Group
91
+ desc "A group with the same name as a default task"
92
+ def group
93
+ puts "group"
94
+ end
95
+ end
File without changes
@@ -1,5 +1,5 @@
1
1
  class MyScript < Thor
2
- check_unknown_options!
2
+ check_unknown_options! :except => :with_optional
3
3
 
4
4
  attr_accessor :some_attribute
5
5
  attr_writer :another_attribute
@@ -26,6 +26,11 @@ class MyScript < Thor
26
26
  [type]
27
27
  end
28
28
 
29
+ desc "hidden TYPE", "this is hidden", :hide => true
30
+ def hidden(type)
31
+ [type]
32
+ end
33
+
29
34
  desc "foo BAR", <<END
30
35
  do some fooing
31
36
  This is more info!
@@ -37,6 +42,7 @@ END
37
42
  end
38
43
 
39
44
  desc "example_default_task", "example!"
45
+ method_options :with => :string
40
46
  def example_default_task
41
47
  options.empty? ? "default task" : options
42
48
  end
@@ -52,6 +58,12 @@ END
52
58
  end
53
59
 
54
60
  desc "long_description", "a" * 80
61
+ long_desc <<-D
62
+ This is a really really really long description.
63
+ Here you go. So very long.
64
+
65
+ It even has two paragraphs.
66
+ D
55
67
  def long_description
56
68
  end
57
69
 
@@ -60,6 +72,10 @@ END
60
72
  end
61
73
 
62
74
  method_options :all => :boolean
75
+ method_option :lazy, :lazy_default => "yes"
76
+ method_option :lazy_numeric, :type => :numeric, :lazy_default => 42
77
+ method_option :lazy_array, :type => :array, :lazy_default => %w[eat at joes]
78
+ method_option :lazy_hash, :type => :hash, :lazy_default => {'swedish' => 'meatballs'}
63
79
  desc "with_optional NAME", "invoke with optional name"
64
80
  def with_optional(name=nil)
65
81
  [ name, options ]
@@ -114,6 +130,24 @@ class MyChildScript < MyScript
114
130
  remove_task :boom, :undefine => true
115
131
  end
116
132
 
133
+ class Barn < Thor
134
+ desc "open [ITEM]", "open the barn door"
135
+ def open(item = nil)
136
+ if item == "shotgun"
137
+ puts "That's going to leave a mark."
138
+ else
139
+ puts "Open sesame!"
140
+ end
141
+ end
142
+
143
+ desc "paint [COLOR]", "paint the barn"
144
+ method_option :coats, :type => :numeric, :default => 2, :desc => 'how many coats of paint'
145
+ def paint(color='red')
146
+ puts "#{options[:coats]} coats of #{color} paint"
147
+ end
148
+
149
+ end
150
+
117
151
  module Scripts
118
152
  class MyScript < MyChildScript
119
153
  argument :accessor, :type => :string
@@ -126,11 +160,21 @@ module Scripts
126
160
  end
127
161
 
128
162
  class MyDefaults < Thor
163
+ check_unknown_options!
164
+
129
165
  namespace :default
130
- desc "test", "prints 'test'"
131
- def test
132
- puts "test"
166
+ desc "cow", "prints 'moo'"
167
+ def cow
168
+ puts "moo"
133
169
  end
170
+
171
+ desc "task_conflict", "only gets called when prepended with a colon"
172
+ def task_conflict
173
+ puts "task"
174
+ end
175
+
176
+ desc "barn", "commands to manage the barn"
177
+ subcommand "barn", Barn
134
178
  end
135
179
 
136
180
  class ChildDefault < Thor
data/spec/group_spec.rb CHANGED
@@ -36,11 +36,11 @@ describe Thor::Group do
36
36
 
37
37
  describe "#desc" do
38
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"
39
+ MyCounter.desc.must == "Description:\n This generator runs three tasks: one, two and three.\n"
40
40
  end
41
41
 
42
42
  it "can be inherited" do
43
- BrokenCounter.desc.must == "Description:\n This generator run three tasks: one, two and three.\n"
43
+ BrokenCounter.desc.must == "Description:\n This generator runs three tasks: one, two and three.\n"
44
44
  end
45
45
 
46
46
  it "can be nil" do
@@ -59,7 +59,7 @@ describe Thor::Group do
59
59
 
60
60
  it "shows description" do
61
61
  @content.must =~ /Description:/
62
- @content.must =~ /This generator run three tasks: one, two and three./
62
+ @content.must =~ /This generator runs three tasks: one, two and three./
63
63
  end
64
64
 
65
65
  it "shows options information" do
@@ -37,13 +37,6 @@ describe Thor::Invocation do
37
37
  base.invoke(B, :one, ["Jose"]).must == "Valim, Jose"
38
38
  end
39
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
40
  it "allows customized options to be given" do
48
41
  base = A.new([], :last_name => "Wrong")
49
42
  base.invoke(B, :one, ["Jose"], :last_name => "Valim").must == "Valim, Jose"
@@ -91,7 +84,7 @@ describe Thor::Invocation do
91
84
 
92
85
  it "raises Thor::UndefinedTaskError if the task can't be found even if all tasks where already executed" do
93
86
  base = C.new
94
- silence(:stdout){ base.invoke }
87
+ silence(:stdout){ base.invoke_all }
95
88
 
96
89
  lambda do
97
90
  base.invoke("foo:bar")
@@ -6,8 +6,8 @@ describe Thor::Option do
6
6
  Thor::Option.parse(key, value)
7
7
  end
8
8
 
9
- def option(name, description=nil, required=false, type=nil, default=nil, banner=nil, group=nil, aliases=[])
10
- @option ||= Thor::Option.new(name, description, required, type, default, banner, group, aliases)
9
+ def option(name, *args)
10
+ @option ||= Thor::Option.new(name, *args)
11
11
  end
12
12
 
13
13
  describe "#parse" do
@@ -47,6 +47,11 @@ describe Thor::Options do
47
47
  it "accepts hashes" do
48
48
  Thor::Options.to_switches(:count => {:a => :b}).must == "--count a:b"
49
49
  end
50
+
51
+ it "accepts underscored options" do
52
+ Thor::Options.to_switches(:under_score_option => "foo bar").must == '--under_score_option "foo bar"'
53
+ end
54
+
50
55
  end
51
56
 
52
57
  describe "#parse" do
@@ -115,6 +120,18 @@ describe Thor::Options do
115
120
  parse("--asdf---asdf", "baz", "--foo", "--asdf---dsf--asdf").must == {"foo" => "--asdf---dsf--asdf"}
116
121
  check_unknown!
117
122
  end
123
+
124
+ it "excepts underscores in commandline args hash for boolean" do
125
+ create :foo_bar => :boolean
126
+ parse("--foo_bar")["foo_bar"].must == true
127
+ parse("--no_foo_bar")["foo_bar"].must == false
128
+ end
129
+
130
+ it "excepts underscores in commandline args hash for strings" do
131
+ create :foo_bar => :string, :baz_foo => :string
132
+ parse("--foo_bar", "baz")["foo_bar"].must == "baz"
133
+ parse("--baz_foo", "foo bar")["baz_foo"].must == "foo bar"
134
+ end
118
135
 
119
136
  describe "with no input" do
120
137
  it "and no switches returns an empty hash" do
@@ -170,12 +187,22 @@ describe Thor::Options do
170
187
  parse("--foo=12")["foo"].must == "12"
171
188
  parse("--foo=bar=baz")["foo"].must == "bar=baz"
172
189
  end
190
+
191
+ it "must accept underscores switch=value assignment" do
192
+ create :foo_bar => :required
193
+ parse("--foo_bar=http://example.com/under_score/")["foo_bar"].must == "http://example.com/under_score/"
194
+ end
173
195
 
174
196
  it "accepts a --no-switch format" do
175
197
  create "--foo" => "bar"
176
198
  parse("--no-foo")["foo"].must be_nil
177
199
  end
178
200
 
201
+ it "does not consume an argument for --no-switch format" do
202
+ create "--cheese" => :string
203
+ parse('burger', '--no-cheese', 'fries')["cheese"].must be_nil
204
+ end
205
+
179
206
  it "accepts a --switch format on non required types" do
180
207
  create "--foo" => :string
181
208
  parse("--foo")["foo"].must == "foo"
data/spec/runner_spec.rb CHANGED
@@ -30,9 +30,8 @@ describe Thor::Runner do
30
30
  end
31
31
 
32
32
  it "raises error if a class/task cannot be found" do
33
- Thor::Runner.should_receive(:exit).with(1)
34
33
  content = capture(:stderr){ Thor::Runner.start(["help", "unknown"]) }
35
- content.strip.must == 'Could not find namespace or task "unknown".'
34
+ content.strip.must == 'Could not find task "unknown" in "default" namespace.'
36
35
  end
37
36
  end
38
37
 
@@ -68,10 +67,9 @@ describe Thor::Runner do
68
67
  end
69
68
 
70
69
  it "raises an error if class/task can't be found" do
71
- Thor::Runner.should_receive(:exit).with(1)
72
70
  ARGV.replace ["unknown"]
73
71
  content = capture(:stderr){ Thor::Runner.start }
74
- content.strip.must == 'Could not find namespace or task "unknown".'
72
+ content.strip.must == 'Could not find task "unknown" in "default" namespace.'
75
73
  end
76
74
 
77
75
  it "does not swallow NoMethodErrors that occur inside the called method" do
@@ -87,7 +85,7 @@ describe Thor::Runner do
87
85
  it "does not swallow Thor InvocationError" do
88
86
  ARGV.replace ["my_script:animal"]
89
87
  content = capture(:stderr) { Thor::Runner.start }
90
- content.strip.must == '"animal" was called incorrectly. Call as "my_script:animal TYPE".'
88
+ content.strip.must == '"animal" was called incorrectly. Call as "thor my_script:animal TYPE".'
91
89
  end
92
90
  end
93
91
 
@@ -145,12 +143,22 @@ describe Thor::Runner do
145
143
 
146
144
  it "presents tasks in the default namespace with an empty namespace" do
147
145
  ARGV.replace ["list"]
148
- capture(:stdout) { Thor::Runner.start }.must =~ /^thor :test\s+# prints 'test'/m
146
+ capture(:stdout) { Thor::Runner.start }.must =~ /^thor :cow\s+# prints 'moo'/m
149
147
  end
150
148
 
151
149
  it "runs tasks with an empty namespace from the default namespace" do
152
- ARGV.replace [":test"]
153
- capture(:stdout) { Thor::Runner.start }.must == "test\n"
150
+ ARGV.replace [":task_conflict"]
151
+ capture(:stdout) { Thor::Runner.start }.must == "task\n"
152
+ end
153
+
154
+ it "runs groups even when there is a task with the same name" do
155
+ ARGV.replace ["task_conflict"]
156
+ capture(:stdout) { Thor::Runner.start }.must == "group\n"
157
+ end
158
+
159
+ it "runs tasks with no colon in the default namespace" do
160
+ ARGV.replace ["cow"]
161
+ capture(:stdout) { Thor::Runner.start }.must == "moo\n"
154
162
  end
155
163
  end
156
164