Skip to content

Commit 8e296f6

Browse files
Tom Finilltmfnll
authored andcommitted
Allow for nested no_commands blocks.
In certain circumstances we may wish to nest `no_commands` blocks. For example, if we want to import any module which includes both methods and `attr_reader` invocations. However, since when exiting the no_commands block the @no_commands instance var is reset to `false` we get unexpected behaviour. This change uses a `NestedContext` object to track the depth of the no_command blocks and ensure that we only start creating commands again once we've left all of them.
1 parent 181e91d commit 8e296f6

File tree

5 files changed

+70
-9
lines changed

5 files changed

+70
-9
lines changed

lib/thor/base.rb

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require_relative "core_ext/hash_with_indifferent_access"
33
require_relative "error"
44
require_relative "invocation"
5+
require_relative "nested_context"
56
require_relative "parser"
67
require_relative "shell"
78
require_relative "line_editor"
@@ -418,14 +419,20 @@ def remove_command(*names)
418419
# remove_command :this_is_not_a_command
419420
# end
420421
#
421-
def no_commands
422-
@no_commands = true
423-
yield
424-
ensure
425-
@no_commands = false
422+
def no_commands(&block)
423+
no_commands_context.enter(&block)
426424
end
425+
427426
alias_method :no_tasks, :no_commands
428427

428+
def no_commands_context
429+
@no_commands_context ||= NestedContext.new
430+
end
431+
432+
def no_commands?
433+
no_commands_context.entered?
434+
end
435+
429436
# Sets the namespace for the Thor or Thor::Group class. By default the
430437
# namespace is retrieved from the class name. If your Thor class is named
431438
# Scripts::MyScript, the help method, for example, will be called as:
@@ -607,7 +614,7 @@ def find_and_refresh_command(name) #:nodoc:
607614
def inherited(klass)
608615
super(klass)
609616
Thor::Base.register_klass_file(klass)
610-
klass.instance_variable_set(:@no_commands, false)
617+
klass.instance_variable_set(:@no_commands, 0)
611618
end
612619

613620
# Fire this callback whenever a method is added. Added methods are
@@ -624,8 +631,7 @@ def method_added(meth)
624631
# Return if it's not a public instance method
625632
return unless public_method_defined?(meth.to_sym)
626633

627-
@no_commands ||= false
628-
return if @no_commands || !create_command(meth)
634+
return if no_commands? || !create_command(meth)
629635

630636
is_thor_reserved_word?(meth, :command)
631637
Thor::Base.register_klass_file(self)

lib/thor/nested_context.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
class Thor
2+
class NestedContext
3+
def initialize
4+
@depth = 0
5+
end
6+
7+
def enter
8+
push
9+
10+
yield
11+
ensure
12+
pop
13+
end
14+
15+
def entered?
16+
@depth > 0
17+
end
18+
19+
private
20+
21+
def push
22+
@depth += 1
23+
end
24+
25+
def pop
26+
@depth -= 1
27+
end
28+
end
29+
end

spec/base_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def hello
4949
it "avoids methods being added as commands" do
5050
expect(MyScript.commands.keys).to include("animal")
5151
expect(MyScript.commands.keys).not_to include("this_is_not_a_command")
52+
expect(MyScript.commands.keys).not_to include("neither_is_this")
5253
end
5354
end
5455

spec/fixtures/script.thor

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ class MyScript < Thor
2828
desc "animal TYPE", "horse around"
2929

3030
no_commands do
31-
def this_is_not_a_command
31+
no_commands do
32+
def this_is_not_a_command
33+
end
34+
end
35+
36+
def neither_is_this
3237
end
3338
end
3439

spec/nested_context_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require "helper"
2+
3+
describe Thor::NestedContext do
4+
subject(:context) { described_class.new }
5+
6+
describe "#enter" do
7+
it "is never empty within the entered block" do
8+
context.enter do
9+
context.enter {}
10+
11+
expect(context).to be_entered
12+
end
13+
end
14+
15+
it "is empty when outside of all blocks" do
16+
context.enter { context.enter {} }
17+
expect(context).not_to be_entered
18+
end
19+
end
20+
end

0 commit comments

Comments
 (0)