diff --git a/lib/pry/command_base.rb b/lib/pry/command_base.rb new file mode 100644 index 00000000..c4b01805 --- /dev/null +++ b/lib/pry/command_base.rb @@ -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 diff --git a/lib/pry/commands.rb b/lib/pry/commands.rb index f6069b64..2b6a9ce5 100644 --- a/lib/pry/commands.rb +++ b/lib/pry/commands.rb @@ -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 .inspect", - "cd" => "Start a Pry session on (use `cd ..` to go back)", - "show_doc" => "Show the comments above ", - "show_idoc" => "Show the comments above instance method ", - "show_method" => "Show sourcecode for method ", - "show_imethod" => "Show sourcecode for instance method ", - "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 .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 (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 " + 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 " + 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 ." + 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 ." + 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 diff --git a/lib/pry/completion.rb b/lib/pry/completion.rb index 8c0701e0..e45a839f 100644 --- a/lib/pry/completion.rb +++ b/lib/pry/completion.rb @@ -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 diff --git a/lib/pry/pry_class.rb b/lib/pry/pry_class.rb index ad1b6135..e18d02f7 100644 --- a/lib/pry/pry_class.rb +++ b/lib/pry/pry_class.rb @@ -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 diff --git a/lib/pry/pry_instance.rb b/lib/pry/pry_instance.rb index 6e9820bf..a17b60a9 100644 --- a/lib/pry/pry_instance.rb +++ b/lib/pry/pry_instance.rb @@ -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] :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 diff --git a/lib/pry/version.rb b/lib/pry/version.rb index e6deb204..48b5ca6b 100644 --- a/lib/pry/version.rb +++ b/lib/pry/version.rb @@ -1,3 +1,3 @@ class Pry - VERSION = "0.4.0" + VERSION = "0.4.0pre1" end diff --git a/test/test.rb b/test/test.rb index f83abafb..1b07a649 100644 --- a/test/test.rb +++ b/test/test.rb @@ -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 } diff --git a/test/test_helper.rb b/test/test_helper.rb index 12a0b339..80eca244 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -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