diff --git a/lib/pry/command_processor.rb b/lib/pry/command_processor.rb index 36b8074d..72369b5e 100644 --- a/lib/pry/command_processor.rb +++ b/lib/pry/command_processor.rb @@ -145,15 +145,20 @@ class Pry args = [] end - options = { + context = { :val => val, :arg_string => arg_string, :eval_string => eval_string, :commands => commands.commands, - :captures => captures + :captures => captures, + :pry_instance => @pry_instance, + :output => output, + :command_processor => self, + :command_set => commands, + :target => target } - ret = execute_command(target, command, options, *(captures + args)) + ret = execute_command(command, context, *(captures + args)) Result.new(true, command.options[:keep_retval], ret) end @@ -165,40 +170,19 @@ class Pry # @param [Hash] options The options to set on the Commands object. # @param [Array] args The command arguments. # @return [Object] The value returned by the command - def execute_command(target, command, options, *args) + def execute_command(command, context, *args) ret = nil - # allocate, setup and then call initialize so that authors - # can do their own setup in initialize. - context = command.allocate - setup_context(target, command, context, options) - context.extend commands.helper_module - context.send(:initialize) + instance = command.new(context) catch(:command_done) do - ret = context.call(*args) + ret = instance.call_with_hooks(*args) end # FIXME: wtf? - options[:val].replace("") + context[:val].replace("") ret end - - def setup_context(target, command, context, options) - context.opts = options - context.target = target - context.target_self = target.eval('self') - context.output = output - context.captures = options[:captures] - context.eval_string = options[:eval_string] - context.arg_string = options[:arg_string] - context.command_set = commands - context.command_name = command.options[:listing] - - context._pry_ = @pry_instance - - context.command_processor = self - end end end diff --git a/lib/pry/command_set.rb b/lib/pry/command_set.rb index a5d421ec..bd23889b 100644 --- a/lib/pry/command_set.rb +++ b/lib/pry/command_set.rb @@ -18,23 +18,58 @@ class Pry attr_accessor :block end + attr_accessor :context + %w(name description options block).each do |attribute| define_method(attribute) { self.class.send(attribute) } end class << self def inspect - "#" + "#" end - def subclass(name, description, options, &block) + def subclass(name, description, options, helpers, &block) klass = Class.new(self) + klass.send(:include, helpers) klass.name = name klass.description = description klass.options = options klass.block = block klass end + + def hooks + @hooks ||= {:before => [], :after => []} + end + end + + def initialize(context) + self.context = context + self.target = context[:target] + self.target_self = context[:target].eval('self') + self.output = context[:output] + self.captures = context[:captures] + self.eval_string = context[:eval_string] + self.arg_string = context[:arg_string] + self.command_set = context[:command_set] + self.command_name = self.class.options[:listing] + self._pry_ = context[:pry_instance] + self.command_processor = context[:command_processor] + end + + def call_with_hooks(*args) + self.class.hooks[:before].each do |block| + instance_exec(*args, &block) + end + + ret = call *args + + self.class.hooks[:after].each do |block| + ret = instance_exec(*args, &block) + end + + self.class.options[:keep_retval] ? ret : CommandContext::VOID_VALUE end end @@ -49,6 +84,9 @@ class Pry end class BlockCommand < Command + # backwards compatibility + alias_method :opts, :context + def call(*args) if options[:argument_required] && args.empty? raise CommandError, "The command '#{command.name}' requires an argument." @@ -75,6 +113,8 @@ class Pry attr_accessor :args def call(*args) + setup + self.opts = slop self.args = self.opts.parse!(args) @@ -85,7 +125,9 @@ class Pry end end + def setup; end def options(opt); end + def run; raise CommandError, "command '#{name}' not implemented" end def slop Slop.new do |opt| @@ -94,7 +136,6 @@ class Pry end end - def run; raise CommandError, "command '#{name}' not implemented" end end include Enumerable @@ -182,9 +223,9 @@ class Pry }.merge!(options) if command_dependencies_met? options - commands[name] = BlockCommand.subclass(name, description, options, &block) + commands[name] = BlockCommand.subclass(name, description, options, helper_module, &block) else - commands[name] = StubCommand.subclass(name, description, options) + commands[name] = StubCommand.subclass(name, description, options, helper_module) end end @@ -200,10 +241,10 @@ class Pry }.merge!(options) if command_dependencies_met? options - commands[name] = ClassCommand.subclass(name, description, options) + commands[name] = ClassCommand.subclass(name, description, options, helper_module) commands[name].class_eval(&block) else - commands[name] = StubCommand.subclass(name, description, options) + commands[name] = StubCommand.subclass(name, description, options, helper_module) end end @@ -218,18 +259,7 @@ class Pry # end def before_command(name, &block) cmd = find_command_by_name_or_listing(name) - prev_callable = cmd.callable - - wrapper_block = proc do |*args| - instance_exec(*args, &block) - - if prev_callable.is_a?(Proc) - instance_exec(*args, &prev_callable) - else - prev_callable.call(*args) - end - end - cmd.callable = wrapper_block + cmd.hooks[:before].unshift block end # Execute a block of code after a command is invoked. The block also @@ -243,18 +273,7 @@ class Pry # end def after_command(name, &block) cmd = find_command_by_name_or_listing(name) - prev_callable = cmd.callable - - wrapper_block = proc do |*args| - if prev_callable.is_a?(Proc) - instance_exec(*args, &prev_callable) - else - prev_callable.call(*args) - end - - instance_exec(*args, &block) - end - cmd.callable = wrapper_block + cmd.hooks[:after] << block end def each &block @@ -382,6 +401,11 @@ class Pry commands.keys end + def run_command(context, name, *args) + command = commands[name] or raise NoCommandError.new(name, self) + command.new(context).call_with_hooks(*args) + end + private def define_default_commands diff --git a/test/test_class_based_commands.rb b/test/test_class_based_commands.rb deleted file mode 100644 index b93b6b91..00000000 --- a/test/test_class_based_commands.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'helper' - -# integration tests -describe "integration tests for class-based commands" do - before do - @set = Pry::CommandSet.new - end - - it 'should invoke a class-based command from the REPL' do - c = Class.new(Pry::CommandContext) do - def call - output.puts "yippee!" - end - end - - @set.command 'foo', "desc", :definition => c.new - @set.import_from Pry::Commands, "exit-all" - - redirect_pry_io(InputTester.new("foo", "exit-all"), out =StringIO.new) do - Pry.start binding, :commands => @set - end - - out.string.should =~ /yippee!/ - end - - it 'should return specified value with :keep_retval => true' do - c = Class.new(Pry::CommandContext) do - def call - :i_enjoyed_the_song_new_flame_by_simply_red_as_a_child_wandering_around_supermarkets - end - end - - @set.command 'foo', "desc", :keep_retval => true, :definition => c.new - @set.import_from Pry::Commands, "exit-all" - - redirect_pry_io(InputTester.new("foo", "exit-all"), out =StringIO.new) do - Pry.start binding, :commands => @set - end - - out.string.should =~ /i_enjoyed_the_song_new_flame_by_simply_red_as_a_child_wandering_around_supermarkets/ - end - - it 'should NOT return specified value with :keep_retval => false' do - c = Class.new(Pry::CommandContext) do - def call - :i_enjoyed_the_song_new_flame_by_simply_red_as_a_child_wandering_around_supermarkets - end - end - - @set.command 'foo', "desc", :keep_retval => false, :definition => c.new - @set.import_from Pry::Commands, "exit-all" - - redirect_pry_io(InputTester.new("foo", "exit-all"), out =StringIO.new) do - Pry.start binding, :commands => @set - end - - out.string.should.not =~ /i_enjoyed_the_song_new_flame_by_simply_red_as_a_child_wandering_around_supermarkets/ - end - - -end diff --git a/test/test_command_set.rb b/test/test_command_set.rb index 70f517a3..1a03e1d9 100644 --- a/test/test_command_set.rb +++ b/test/test_command_set.rb @@ -3,7 +3,10 @@ require 'helper' describe Pry::CommandSet do before do @set = Pry::CommandSet.new - @ctx = Pry::CommandContext.new + @ctx = { + :target => binding, + :command_set => @set + } end it 'should call the block used for the command when it is called' do @@ -24,11 +27,11 @@ describe Pry::CommandSet do @set.run_command @ctx, 'foo', 1, 2, 3 end - it 'should use the first argument as self' do + it 'should use the first argument as context' do ctx = @ctx @set.command 'foo' do - self.should == ctx + self.context.should == ctx end @set.run_command @ctx, 'foo' @@ -241,8 +244,8 @@ describe Pry::CommandSet do end it "should provide a 'help' command" do - @ctx.command_set = @set - @ctx.output = StringIO.new + @ctx[:command_set] = @set + @ctx[:output] = StringIO.new lambda { @set.run_command(@ctx, 'help') @@ -255,12 +258,12 @@ describe Pry::CommandSet do @set.command 'moo', "Mooerizes" do; end @set.command 'boo', "Booerizes" do; end - @ctx.command_set = @set - @ctx.output = StringIO.new + @ctx[:command_set] = @set + @ctx[:output] = StringIO.new @set.run_command(@ctx, 'help') - doc = @ctx.output.string + doc = @ctx[:output].string order = [doc.index("boo"), doc.index("foo"), @@ -331,15 +334,15 @@ describe Pry::CommandSet do end it 'should share the context with the original command' do - @ctx.target = "test target string" + @ctx[:target] = "test target string".__binding__ before_val = nil orig_val = nil @set.command('foo') { orig_val = target } @set.before_command('foo') { before_val = target } @set.run_command(@ctx, 'foo') - before_val.should == @ctx.target - orig_val.should == @ctx.target + before_val.should == @ctx[:target] + orig_val.should == @ctx[:target] end it 'should work when applied multiple times' do @@ -375,15 +378,15 @@ describe Pry::CommandSet do end it 'should share the context with the original command' do - @ctx.target = "test target string" + @ctx[:target] = "test target string".__binding__ after_val = nil orig_val = nil @set.command('foo') { orig_val = target } @set.after_command('foo') { after_val = target } @set.run_command(@ctx, 'foo') - after_val.should == @ctx.target - orig_val.should == @ctx.target + after_val.should == @ctx[:target] + orig_val.should == @ctx[:target] end it 'should determine the return value for the command' do @@ -418,149 +421,4 @@ describe Pry::CommandSet do end end - - describe "class-based commands" do - it 'should pass arguments to the command' do - c = Class.new(Pry::CommandContext) do - def call(*args) - args.should == [1, 2, 3] - end - end - - @set.command 'foo', "desc", :definition => c.new - - ctx = @set.commands['foo'].callable - @set.run_command ctx, 'foo', 1, 2, 3 - end - - it 'should set unprovided arguments to nil' do - c = Class.new(Pry::CommandContext) do - def call(x, y, z) - x.should == 1 - y.should == nil - z.should == nil - end - end - - @set.command 'foo', "desc", :definition => c.new - - ctx = @set.commands['foo'].callable - @set.run_command ctx, 'foo', 1 - end - - it 'should clip provided arguments to expected number' do - c = Class.new(Pry::CommandContext) do - def call(x, y, z) - x.should == 1 - y.should == 2 - end - end - - @set.command 'foo', "desc", :definition => c.new - - ctx = @set.commands['foo'].callable - @set.run_command ctx, 'foo', 1, 2, 3, 4 - end - - it 'should return Pry::CommandContext::VOID by default' do - c = Class.new(Pry::CommandContext) do - def call - :i_have_done_thing_i_regret - end - end - - @set.command 'foo', "desc", :definition => c.new - - ctx = @set.commands['foo'].callable - @set.run_command(ctx, 'foo').should == Pry::CommandContext::VOID_VALUE - end - - it 'should return specific value when :keep_retval => true' do - c = Class.new(Pry::CommandContext) do - def call - :i_have_a_dog_called_tobina - end - end - - @set.command 'foo', "desc", :keep_retval => true, :definition => c.new - - ctx = @set.commands['foo'].callable - @set.run_command(ctx, 'foo').should == :i_have_a_dog_called_tobina - end - - it 'should have access to helper methods' do - c = Class.new(Pry::CommandContext) do - def call - im_helping.should == "butterbum" - end - end - - @set.command 'foo', "desc", :definition => c.new - - @set.helpers do - def im_helping - "butterbum" - end - end - - ctx = @set.commands['foo'].callable - @set.run_command ctx, 'foo' - end - - it 'should persist state' do - c = Class.new(Pry::CommandContext) do - attr_accessor :state - def call - @state ||= 0 - @state += 1 - end - end - - @set.command 'foo', "desc", :definition => c.new - - ctx = @set.commands['foo'].callable - @set.run_command ctx, 'foo' - @set.run_command ctx, 'foo' - ctx.state.should == 2 - end - - describe "before_command" do - it 'should be called before the original command' do - foo = [] - c = Class.new(Pry::CommandContext) do - define_method(:call) do - foo << 1 - end - end - - @set.command 'foo', "desc", :definition => c.new - - ctx = @set.commands['foo'].callable - @set.before_command('foo') { foo << 2 } - @set.run_command(ctx, 'foo') - - foo.should == [2, 1] - end - end - - describe "after_command" do - it 'should be called before the original command' do - foo = [] - c = Class.new(Pry::CommandContext) do - define_method(:call) do - foo << 1 - end - end - - @set.command 'foo', "desc", :definition => c.new - - ctx = @set.commands['foo'].callable - @set.after_command('foo') { foo << 2 } - @set.run_command(ctx, 'foo') - - foo.should == [1, 2] - end - end - - end end diff --git a/test/test_pry.rb b/test/test_pry.rb index fcc9f2bf..b9bcb540 100644 --- a/test/test_pry.rb +++ b/test/test_pry.rb @@ -886,7 +886,7 @@ describe Pry do klass = Pry::CommandSet.new do alias_command "help2", "help" end - klass.commands["help2"].callable.should == klass.commands["help"].callable + klass.commands["help2"].block.should == klass.commands["help"].block end it 'should change description of a command using desc' do