added support for passing blocks to commands

* Example, using &block:
 Pry.commands.command "tobina", "desc", :takes_block => true do |&block|
   block.call
 end

(pry)> tobina { puts "hi" }
hi
(pry)>
* Works with process(&block) too (for create_command)
* Works with do/end blocks and {} blocks, both single and multi-line

* Example, using command_block:
  Pry.commands.command "tobina", "desc", :takes_block => true do
    command_block.call
  end
This commit is contained in:
John Mair 2012-02-24 03:30:48 +13:00
parent 85eb6e28be
commit 90d6f3eefb
3 changed files with 227 additions and 4 deletions

View File

@ -162,6 +162,13 @@ class Pry
attr_accessor :command_set
attr_accessor :_pry_
# The block we pass *into* a command if it defines `:takes_block => true`
# @example
# my-command do
# puts "block content"
# end
attr_accessor :command_block
# Run a command from another command.
# @param [String] command_string The string that invokes the command
# @param [Array] args Further arguments to pass to the command
@ -281,9 +288,39 @@ class Pry
self.arg_string = arg_string
self.captures = captures
pass_block(args) if command_options[:takes_block]
call_safely(*(captures + args))
end
# Pass a block argument to a command.
# @param [Array<String>] args The arguments passed to the command.
# We inspect these for a 'do' or a '{' and if we find it we use it
# to start a block input sequence. Once we have a complete
# block, we save it to an accessor that can be retrieved from the command context.
# Note that if we find the 'do' or '{' we delete this and the
# elements following it from `args`.
def pass_block(args)
if !(idx = args.find_index { |v| /^do;?$/ =~ v })
idx = args.find_index { |v| /^\{;?$/ =~ v }
end
if idx
block_init_string = args.slice!(idx..-1).join(" ")
prime_string = "proc #{block_init_string}\n"
if !_pry_.complete_expression?(prime_string)
command_string = _pry_.r(target, prime_string)
else
command_string = prime_string
end
self.command_block = target.eval(command_string)
end
end
private :pass_block
# Run the command with the given {args}.
#
# This is a public wrapper around {#call} which ensures all preconditions are met.
@ -367,7 +404,11 @@ class Pry
# @param *String the arguments passed
# @return Object the return value of the block
def call(*args)
instance_exec(*correct_arg_arity(block.arity, args), &block)
# we need to define this method so we can pass a block in (not
# possible with `instance_exec`)
class << self; self; end.send(:define_method, :dummy, &block)
dummy(*correct_arg_arity(block.arity, args), &command_block)
end
def help; description; end
@ -403,7 +444,7 @@ class Pry
output.puts slop.help
void
else
process(*correct_arg_arity(method(:process).arity, args))
process(*correct_arg_arity(method(:process).arity, args), &command_block)
end
end

View File

@ -16,6 +16,14 @@ $VERBOSE = nil
# Ensure we do not execute any rc files
Pry::RC_FILES.clear
# inject a variable into a binding
def inject_var(name, value, b)
Thread.current[:__pry_local__] = value
b.eval("#{name} = Thread.current[:__pry_local__]")
ensure
Thread.current[:__pry_local__] = nil
end
# in case the tests call reset_defaults, ensure we reset them to
# amended (test friendly) values
class << Pry

View File

@ -340,5 +340,179 @@ describe "Pry::Command" do
cmd.new.process_line %(grumpos)
}.should.raise(Pry::CommandError)
end
end
end
end
describe "block parameters" do
before do
@context = Object.new
@set.command "walking-spanish", "down the hall", :takes_block => true do |&block|
inject_var(:@x, block.call, target)
end
@set.import Pry::Commands
end
describe ":takes_block option" do
it 'should accept block when :takes_block => true' do
redirect_pry_io(InputTester.new("walking-spanish { :jesus }", "exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == :jesus
end
it 'should NOT accept block when :takes_block => false' do
@set.command "walking-spanish", "down the hall", :takes_block => false do |&block|
inject_var(:@x, block, target)
end
redirect_pry_io(InputTester.new("walking-spanish { :jesus }", "exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == nil
end
end
it 'should accept multiline blocks' do
redirect_pry_io(InputTester.new("walking-spanish do",
" :jesus",
"end",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == :jesus
end
describe "single line blocks" do
it 'should accept blocks with do ; end' do
redirect_pry_io(InputTester.new("walking-spanish do ; :jesus; end",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == :jesus
end
it 'should accept blocks with do; end' do
redirect_pry_io(InputTester.new("walking-spanish do; :jesus; end",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == :jesus
end
it 'should accept blocks with { }' do
redirect_pry_io(InputTester.new("walking-spanish { :jesus }",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == :jesus
end
end
describe "block-related content removed from arguments" do
describe "block_command" do
it "should remove block-related content from arguments" do
@set.block_command "walking-spanish", "down the hall", :takes_block => true do |x, y|
inject_var(:@x, x, target)
inject_var(:@y, y, target)
end
redirect_pry_io(InputTester.new("walking-spanish { :jesus }",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == nil
@context.instance_variable_get(:@y).should == nil
end
it "should NOT remove block-related content from arguments if :takes_block => false" do
@set.block_command "walking-spanish", "down the hall", :takes_block => false do |x, y|
inject_var(:@x, x, target)
inject_var(:@y, y, target)
end
redirect_pry_io(InputTester.new("walking-spanish { :jesus }",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == "{"
@context.instance_variable_get(:@y).should == ":jesus"
end
end
describe "create_command" do
it "should remove block-related content from arguments" do
@set.create_command "walking-spanish", "down the hall", :takes_block => true do
def process(x, y)
inject_var(:@x, x, target)
inject_var(:@y, y, target)
end
end
redirect_pry_io(InputTester.new("walking-spanish { :jesus }",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == nil
@context.instance_variable_get(:@y).should == nil
end
it "should NOT remove block-related content from arguments if :takes_block => false" do
@set.create_command "walking-spanish", "down the hall", :takes_block => false do
def process(x, y)
inject_var(:@x, x, target)
inject_var(:@y, y, target)
end
end
redirect_pry_io(InputTester.new("walking-spanish { :jesus }",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == "{"
@context.instance_variable_get(:@y).should == ":jesus"
end
end
end
describe "exposing block parameter" do
describe "block_command" do
it "should expose block in command_block method" do
@set.block_command "walking-spanish", "down the hall", :takes_block => true do
inject_var(:@x, command_block.call, target)
end
redirect_pry_io(InputTester.new("walking-spanish { :jesus }",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == :jesus
end
# test for &block already completed many times in other sections
end
describe "create_command" do
it "should expose &block in create_command's process method" do
@set.create_command "walking-spanish", "down the hall", :takes_block => true do
def process(&block)
inject_var(:@x, block.call, target)
end
end
redirect_pry_io(InputTester.new("walking-spanish { :jesus }",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == :jesus
end
it "should expose block in command_block method" do
@set.create_command "walking-spanish", "down the hall", :takes_block => true do
def process
inject_var(:@x, command_block.call, target)
end
end
redirect_pry_io(InputTester.new("walking-spanish { :jesus }",
"exit-all"), out = StringIO.new) do
Pry.start @context, :commands => @set
end
@context.instance_variable_get(:@x).should == :jesus
end
end
end
end
end