saving before refactor to implement action block instead of action proc for commands. All tests passing in 1.8 and 1.9.

This commit is contained in:
John Mair 2011-01-12 12:16:04 +11:00
parent 8e006829fe
commit ebdcfdf145
8 changed files with 359 additions and 275 deletions

80
lib/pry/command_base.rb Normal file
View File

@ -0,0 +1,80 @@
class Pry
class CommandBase
class << self
attr_accessor :commands
attr_accessor :command_info
end
class Command
Elements = [:name, :describe, :pattern, :action]
Elements.each do |e|
define_method(e) { |s| instance_variable_set("@#{e}", s) }
define_method("get_#{e}") { instance_variable_get("@#{e}") }
end
# define action here since it needs to take a block
def action(&block)
@action = block
end
end
def self.check_command(c)
c.pattern(c.get_name) if !c.get_pattern
c.describe "No description." if !c.get_describe
Command::Elements.each do |e|
raise "command has no #{e}!" if !c.send("get_#{e}")
end
end
def self.command(name, &block)
@commands ||= {}
@command_info ||= {}
c = Command.new
c.name name
c.instance_eval(&block)
check_command(c)
@commands.merge! c.get_pattern => c.get_action
@command_info.merge! c.get_name => c.get_describe
end
command "help" do
pattern /^help\s*(.+)?/
describe "This menu."
action do |opts|
out = opts[:output]
command_info = opts[:command_info]
param = opts[:captures].first
if !param
out.puts "Command list:"
out.puts "--"
command_info.each do |k, v|
puts "#{Array(k).first}".ljust(18) + v
end
else
key = command_info.keys.find { |v| Array(v).any? { |k| k === param } }
if key
out.puts command_info[key]
else
out.puts "No info for command: #{param}"
end
end
opts[:val].clear
end
end
def self.inherited(klass)
klass.commands = @commands.dup
klass.command_info = @command_info.dup
end
end
end

View File

@ -1,81 +1,12 @@
direc = File.dirname(__FILE__)
require "#{direc}/command_base"
class Pry
class CommandBase
class << self
attr_accessor :commands
attr_accessor :command_info
end
@commands = {}
@command_info = {}
class Command
Elements = [:name, :description, :pattern, :action]
Elements.each do |e|
define_method(e) { |s| instance_variable_set("@#{e}", s) }
define_method("get_#{e}") { instance_variable_get("@#{e}") }
end
end
def self.check_command(c)
c.pattern(c.get_name) if !c.get_pattern
Command::Elements.each do |e|
raise "command has no #{e}!" if !c.send("get_#{e}")
end
end
def self.command(name, &block)
c = Command.new
c.name name
c.instance_eval(&block)
check_command(c)
commands.merge! c.get_pattern => c.get_action
command_info.merge! c.get_name => c.get_description
end
command "help" do
pattern /^help\s*(.+)?/
description "This menu."
action proc { |opts|
out = opts[:output]
command_info = opts[:command_info]
param = opts[:captures].first
puts opts[:captures].inspect
if !param
out.puts "Command list:"
out.puts "--"
command_info.each do |k, v|
puts "#{Array(k).first}".ljust(18) + v
end
else
key = command_info.keys.find { |v| Array(v).any? { |k| k === param } }
if key
out.puts command_info[key]
else
out.puts "No info for command: #{param}"
end
end
opts[:val].clear
}
end
end
# Default commands used by Pry.
# @note
# If you plan to replace the default Commands class with a custom
# one then it must have a `commands` method that returns a Hash.
class Commands
class Commands < CommandBase
# This method returns a hash that defines the commands implemented for the REPL session.
# The hash has the following form:
@ -107,165 +38,183 @@ class Pry
# opts[:output].puts "hello #{opts[:captures].first}"
# }
# end
def commands
@commands ||= {
"!" => proc do |opts|
opts[:output].puts "Refreshed REPL"
opts[:val].clear
opts[:eval_string].clear
end,
"!pry" => proc do |opts|
Pry.start(opts[:target])
opts[:val].clear
end,
["exit_program", "quit_program"] => proc do
exit
end,
/^help\s*(.+)?/ => proc do |opts|
param = opts[:captures].first
self.show_help(opts[:output], param)
opts[:val].clear
end,
"nesting" => proc do |opts|
self.show_nesting(opts[:output], opts[:nesting])
opts[:val].clear
end,
"status" => proc do |opts|
self.show_status(opts[:output], opts[:nesting], opts[:target])
opts[:val].clear
end,
"exit_all" => proc do
throw(:breakout, 0)
end,
["exit", "quit", "back", /^cd\s*\.\./] => proc do |opts|
throw(:breakout, opts[:nesting].level)
end,
"ls" => proc do |opts|
opts[:output].puts "#{opts[:target].eval('Pry.view(local_variables + instance_variables)')}"
opts[:val].clear
end,
/^cat\s+(.+)/ => proc do |opts|
obj = opts[:captures].first
opts[:output].puts opts[:target].eval("#{obj}.inspect")
opts[:val].clear
end,
/^cd\s+(.+)/ => proc do |opts|
obj = opts[:captures].first
throw(:breakout, opts[:nesting].level) if obj == ".."
opts[:target].eval("#{obj}.pry")
opts[:val].clear
end,
/^show_doc\s*(.+)/ => proc do |opts|
meth_name = opts[:captures].first
doc = opts[:target].eval("method(:#{meth_name})").comment
opts[:output].puts doc
opts[:val].clear
end,
/^show_idoc\s*(.+)/ => proc do |opts|
meth_name = opts[:captures].first
doc = opts[:target].eval("instance_method(:#{meth_name})").comment
opts[:val].clear
end,
/^show_method\s*(.+)/ => proc do |opts|
meth_name = opts[:captures].first
code = opts[:target].eval("method(:#{meth_name})").source
opts[:output].puts code
opts[:val].clear
end,
/^show_imethod\s*(.+)/ => proc do |opts|
meth_name = opts[:captures].first
code = opts[:target].eval("instance_method(:#{meth_name})").source
opts[:val].clear
end,
/^jump_to\s*(\d*)/ => proc do |opts|
break_level = opts[:captures].first.to_i
nesting = opts[:nesting]
case break_level
when nesting.level
opts[:output].puts "Already at nesting level #{nesting.level}"
opts[:val].clear
when (0...nesting.level)
throw(:breakout, break_level + 1)
else
max_nest_level = nesting.level - 1
opts[:output].puts "Invalid nest level. Must be between 0 and #{max_nest_level}. Got #{break_level}."
opts[:val].clear
end
end,
"ls_methods" => proc do |opts|
opts[:output].puts "#{Pry.view(opts[:target].eval('public_methods(false)'))}"
opts[:val].clear
end,
"ls_imethods" => proc do |opts|
opts[:output].puts "#{Pry.view(opts[:target].eval('public_instance_methods(false)'))}"
opts[:val].clear
end
}
command "!" do
describe "Refresh the REPL"
action do |opts|
opts[:output].puts "Refreshed REPL"
opts[:eval_string].clear
end
end
def command_info
@command_info ||= {
"!" => "Refresh the REPL.",
"!pry" => "Start a Pry session on current self; this even works mid-expression.",
["exit_program", "quit_program"] => "end the current program.",
"help" => "This menu.",
"nesting" => "Show nesting information.",
"status" => "Show status information.",
"exit_all" => "End all nested Pry sessions",
["exit", "quit", "back", /cd\s*\.\./] => "End the current Pry session.",
"ls" => "Show the list of vars in the current scope.",
"cat" => "Show output of <var>.inspect",
"cd" => "Start a Pry session on <var> (use `cd ..` to go back)",
"show_doc" => "Show the comments above <methname>",
"show_idoc" => "Show the comments above instance method <methname>",
"show_method" => "Show sourcecode for method <methname>",
"show_imethod" => "Show sourcecode for instance method <methname>",
"jump_to" => "Jump to a Pry session further up the stack, exiting all sessions below.",
"ls_methods" => "List public methods defined on class of receiver.",
"ls_imethods" => "List public instance methods defined on receiver."
}
command "!pry" do
describe "Start a Pry session on current self; this even works mid-expression."
action do |opts|
Pry.start(opts[:target])
end
end
def show_help(out, param)
if !param
out.puts "Command list:"
command ["exit_program", "quit_program"] do
describe "End the current program."
action { |opts| exit }
end
command "nesting" do
describe "Show nesting information."
action do |opts|
out = opts[:output]
nesting = opts[:nesting]
out.puts "Nesting status:"
out.puts "--"
command_info.each do |k, v|
puts "#{Array(k).first}".ljust(18) + v
nesting.each do |level, obj|
if level == 0
out.puts "#{level}. #{Pry.view(obj)} (Pry top level)"
else
out.puts "#{level}. #{Pry.view(obj)}"
end
end
else
key = command_info.keys.find { |v| Array(v).any? { |k| k === param } }
if key
out.puts command_info[key]
end
end
command "status" do
describe "Show status information."
action do |opts|
out = opts[:output]
nesting = opts[:nesting]
target = opts[:target]
out.puts "Status:"
out.puts "--"
out.puts "Receiver: #{Pry.view(target.eval('self'))}"
out.puts "Nesting level: #{nesting.level}"
out.puts "Local variables: #{Pry.view(target.eval('local_variables'))}"
out.puts "Pry instance: #{Pry.active_instance}"
out.puts "Last result: #{Pry.view(Pry.last_result)}"
end
end
command "exit_all" do
describe "End all nested Pry sessions."
action { |opts| throw(:breakout, 0) }
end
command "ls" do
describe "Show the list of vars in the current scope."
action do |opts|
opts[:output].puts "#{opts[:target].eval('Pry.view(local_variables + instance_variables)')}"
end
end
command "cat" do
describe "Show output of <var>.inspect."
pattern /^cat\s+(.+)/
action do |opts|
out = opts[:output]
obj = opts[:captures].first
out.puts opts[:target].eval("#{obj}.inspect")
end
end
command "cd" do
pattern /^cd\s+(.+)/
describe "Start a Pry session on <var> (use `cd ..` to go back)"
action do |opts|
obj = opts[:captures].first
throw(:breakout, opts[:nesting].level) if obj == ".."
opts[:target].eval("#{obj}.pry")
end
end
command "show_doc" do
pattern /^show_doc\s*(.+)/
describe "Show the comments above <methname>"
action do |opts|
meth_name = opts[:captures].first
doc = opts[:target].eval("method(:#{meth_name})").comment
opts[:output].puts doc
end
end
command "show_idoc" do
pattern /^show_idoc\s*(.+)/
describe "Show the comments above instance method <methname>"
action do |opts|
meth_name = opts[:captures].first
doc = opts[:target].eval("instance_method(:#{meth_name})").comment
opts[:output].puts doc
end
end
command "show_method" do
pattern /^show_method\s*(.+)/
describe "Show sourcecode for method <methname>."
action do |opts|
meth_name = opts[:captures].first
doc = opts[:target].eval("method(:#{meth_name})").source
opts[:output].puts doc
end
end
command "show_imethod" do
pattern /^show_imethod\s*(.+)/
describe "Show sourcecode for instance method <methname>."
action do |opts|
meth_name = opts[:captures].first
doc = opts[:target].eval("instance_method(:#{meth_name})").source
opts[:output].puts doc
end
end
command "jump_to" do
pattern /^jump_to\s*(\d*)/
describe "Jump to a Pry session further up the stack, exiting all sessions below."
action do |opts|
break_level = opts[:captures].first.to_i
nesting = opts[:nesting]
case break_level
when nesting.level
opts[:output].puts "Already at nesting level #{nesting.level}"
when (0...nesting.level)
throw(:breakout, break_level + 1)
else
out.puts "No info for command: #{param}"
max_nest_level = nesting.level - 1
opts[:output].puts "Invalid nest level. Must be between 0 and #{max_nest_level}. Got #{break_level}."
end
end
end
def show_nesting(out, nesting)
out.puts "Nesting status:"
out.puts "--"
nesting.each do |level, obj|
if level == 0
out.puts "#{level}. #{Pry.view(obj)} (Pry top level)"
else
out.puts "#{level}. #{Pry.view(obj)}"
end
command "ls_methods" do
describe "List public methods defined on class of receiver."
action do |opts|
opts[:output].puts "#{Pry.view(opts[:target].eval('public_methods(false)'))}"
end
end
def show_status(out, nesting, target)
out.puts "Status:"
out.puts "--"
out.puts "Receiver: #{Pry.view(target.eval('self'))}"
out.puts "Nesting level: #{nesting.level}"
out.puts "Local variables: #{Pry.view(target.eval('local_variables'))}"
out.puts "Pry instance: #{Pry.active_instance}"
out.puts "Last result: #{Pry.view(Pry.last_result)}"
command "ls_imethods" do
describe "List public instance methods defined on class of receiver."
action do |opts|
opts[:output].puts "#{Pry.view(opts[:target].eval('public_instance_methods(false)'))}"
end
end
command ["exit", "quit", "back"] do
describe "End the current Pry session."
action do |opts|
throw(:breakout, opts[:nesting].level)
end
end
end
end

View File

@ -1,9 +1,15 @@
# stolen from irb
# taken from irb
require "readline"
class Pry
module InputCompleter
if Readline.respond_to?("basic_word_break_characters=")
Readline.basic_word_break_characters= " \t\n\"\\'`><=;|&{("
end
Readline.completion_append_character = nil
ReservedWords = [
"BEGIN", "END",
@ -30,7 +36,7 @@ class Pry
"[]", "[]=", "^", "!", "!=", "!~"]
def self.build_completion_proc(target, commands=[""])
proc { |input|
proc do |input|
bind = target
case input
@ -75,7 +81,6 @@ class Pry
candidates = Object.constants.collect{|m| m.to_s}
candidates.grep(/^#{receiver}/).collect{|e| "::" + e}
# when /^(((::)?[A-Z][^:.\(]*)+)::?([^:.]*)$/
when /^([A-Z].*)::([^:.]*)$/
# Constant or class methods
receiver = $1
@ -124,8 +129,6 @@ class Pry
regmessage = Regexp.new(Regexp.quote($1))
candidates = global_variables.collect{|m| m.to_s}.grep(regmessage)
# when /^(\$?(\.?[^.]+)+)\.([^.]*)$/
# when /^((\.?[^.]+)+)\.([^.]*)$/
when /^([^."].*)\.([^.]*)$/
# variable
receiver = $1
@ -175,11 +178,9 @@ class Pry
(candidates|ReservedWords|commands).grep(/^#{Regexp.quote(input)}/)
end
}
end
end
def self.select_message(receiver, message, candidates)
candidates.grep(/^#{message}/).collect do |e|
case e
@ -194,7 +195,3 @@ class Pry
end
end
if Readline.respond_to?("basic_word_break_characters=")
Readline.basic_word_break_characters= " \t\n\"\\'`><=;|&{("
end
Readline.completion_append_character = nil

View File

@ -1,3 +1,5 @@
require 'readline'
# @author John Mair (banisterfiend)
class Pry
@ -83,7 +85,7 @@ class Pry
def self.reset_defaults
@input = Readline
@output = $stdout
@commands = Commands.new
@commands = Commands
@prompt = DEFAULT_PROMPT
@print = DEFAULT_PRINT
@hooks = DEFAULT_HOOKS

View File

@ -1,3 +1,5 @@
require 'readline'
class Pry
# The list of configuration options.
@ -10,7 +12,7 @@ class Pry
# @param [Hash] options The optional configuration parameters.
# @option options [#read] :input The object to use for input. (see input.rb)
# @option options [#puts] :output The object to use for output. (see output.rb)
# @option options [#commands] :commands The object to use for
# @option options [Pry::CommandBase] :commands The object to use for
# commands. (see commands.rb)
# @option options [Hash] :hooks The defined hook Procs (see hooks.rb)
# @option options [Array<Proc>] :default_prompt The array of Procs
@ -110,7 +112,6 @@ class Pry
def re(target=TOPLEVEL_BINDING)
target = binding_for(target)
# FIXME!!!!!!!! Should not hardcode command_info in here!
if input == Readline
Readline.completion_proc = Pry::InputCompleter.build_completion_proc(target, Pry.commands.command_info.keys.flatten)
end
@ -167,6 +168,7 @@ class Pry
# @param [Binding] target The receiver of the commands.
def process_commands(val, eval_string, target)
def val.clear() replace("") end
def eval_string.clear() replace("") end
pattern, action = commands.commands.find { |k, v| Array(k).any? { |a| a === val } }
@ -184,6 +186,7 @@ class Pry
}
action.call(options)
val.clear
end
end
@ -191,7 +194,7 @@ class Pry
# This method should not need to be invoked directly.
# @param [String] current_prompt The prompt to use for input.
# @return [String] The next line of input.
def readline(current_prompt)
def readline(current_prompt="> ")
if input == Readline

View File

@ -1,3 +1,3 @@
class Pry
VERSION = "0.4.0"
VERSION = "0.4.0pre1"
end

View File

@ -5,8 +5,6 @@ require 'bacon'
require "#{direc}/../lib/pry"
require "#{direc}/test_helper"
NOT_FOR_RUBY_18 = [/show_doc/, /show_idoc/, /show_method/, /show_imethod/]
puts "Ruby Version #{RUBY_VERSION}"
puts "Testing Pry #{Pry::VERSION}"
puts "With method_source version #{MethodSource::VERSION}"
@ -105,9 +103,9 @@ describe Pry do
describe "commands" do
it 'should run command1' do
pry_tester = Pry.new
pry_tester.commands = CommandTester.new
pry_tester.commands = CommandTester
pry_tester.input = InputTester.new("command1", "exit_all")
pry_tester.commands = CommandTester.new
pry_tester.commands = CommandTester
str_output = StringIO.new
pry_tester.output = str_output
@ -119,9 +117,9 @@ describe Pry do
it 'should run command2' do
pry_tester = Pry.new
pry_tester.commands = CommandTester.new
pry_tester.commands = CommandTester
pry_tester.input = InputTester.new("command2 horsey", "exit_all")
pry_tester.commands = CommandTester.new
pry_tester.commands = CommandTester
str_output = StringIO.new
pry_tester.output = str_output
@ -262,30 +260,68 @@ describe Pry do
str_output2.string.should =~ /7/
end
it 'should set the commands default, and the default should be overridable' do
commands = {
"hello" => proc { |opts| opts[:output].puts "hello world"; opts[:val].clear }
}
def commands.commands() self end
Pry.commands = commands
str_output = StringIO.new
Pry.new(:input => InputTester.new("hello"), :output => str_output).rep
str_output.string.should =~ /hello world/
commands = {
"goodbye" => proc { |opts| opts[:output].puts "goodbye world"; opts[:val].clear }
}
def commands.commands() self end
str_output = StringIO.new
describe "commands" do
Pry.new(:input => InputTester.new("goodbye"), :output => str_output, :commands => commands).rep
str_output.string.should =~ /goodbye world/
end
it 'should set the commands default, and the default should be overridable' do
class Command0 < Pry::CommandBase
command "hello" do
describe ""
action { |opts| opts[:output].puts "hello world"; opts[:val].clear }
end
end
Pry.commands = Command0
str_output = StringIO.new
Pry.new(:input => InputTester.new("hello"), :output => str_output).rep
str_output.string.should =~ /hello world/
class Command1 < Pry::CommandBase
command "goodbye" do
describe ""
action { |opts| opts[:output].puts "goodbye world"; opts[:val].clear }
end
end
str_output = StringIO.new
Pry.new(:input => InputTester.new("goodbye"), :output => str_output, :commands => Command1).rep
str_output.string.should =~ /goodbye world/
Object.remove_const(:Command0)
Object.remove_const(:Command1)
end
it 'should inherit "help" command from Pry::CommandBase' do
class Command2 < Pry::CommandBase
command "h" do |v|
v.describe "h command"
v.action { }
end
end
Command2.commands.keys.size.should == 2
Command2.command_info.keys.include?("help").should == true
Command2.command_info.keys.include?("h").should == true
Object.remove_const(:Command2)
end
it 'should inherit comands from Pry::Commands' do
class Command3 < Pry::Commands
command "v" do
action {}
end
end
Command3.command_info.include?("nesting").should == true
Command3.command_info.include?("jump_to").should == true
Command3.command_info.include?("cd").should == true
Command3.command_info.include?("v").should == true
Object.remove_const(:Command3)
end
end
it "should set the print default, and the default should be overridable" do
new_print = proc { |out, value| out.puts value }

View File

@ -1,6 +1,5 @@
class Object
def test_method
end
class Module
public :remove_const
end
class InputTester
@ -18,15 +17,33 @@ class InputTester
end
end
class CommandTester
def commands
@commands ||= {
"command1" => proc { |opts| opts[:output].puts "command1"; opts[:val].clear },
/command2\s*(.*)/ => proc do |opts|
arg = opts[:captures].first
opts[:output].puts arg
opts[:val].clear
end
class CommandTester < Pry::CommandBase
command "command1" do
describe "command 1 test"
action { |opts| opts[:output].puts "command1"; opts[:val].clear }
end
command "command2" do
describe "command 2 test"
pattern /command2\s*(.*)/
action { |opts|
arg = opts[:captures].first
opts[:output].puts arg
opts[:val].clear
}
end
# def commands
# @commands ||= {
# "command1" => proc { |opts| opts[:output].puts "command1"; opts[:val].clear },
# /command2\s*(.*)/ => proc do |opts|
# arg = opts[:captures].first
# opts[:output].puts arg
# opts[:val].clear
# end
# }
# end
# e
end