Got rid of show_imethod and show_idoc, merged into show-method and show-doc with special -M option. Changed all underscored command names to hyphenated-names, e.g jump-to instead of jump_to.

* added a bunch of new command options to ls command, supercharging it. Using optparse.
* added options to show-method and show-doc
* now showing file/line for show-method, show-doc, show-command commands
* cleaned up 'help', and got rid of aliases from help display; now showing aliases in primary command help output.
This commit is contained in:
John Mair 2011-02-17 05:27:55 +13:00
parent 54cc7ee271
commit f8f0919189
6 changed files with 286 additions and 83 deletions

16
TODO Normal file
View File

@ -0,0 +1,16 @@
0.5.0 release:
* !!!! UPDATE DOCUMENTATION !!!!
* Use clipped version of Pry.view() for large objects
* Exit Pry session on ^d
* Use Shellwords for breaking up parameters to pry commands
* Use OptionParser to parse options for default pry commands
* Add version command
* Refactor 'status' command: add current method info
* Add meth_name_from_binding utility lambda to commands.rb
* Add -g, -m, -v(erbose), -a(ll), -s(uper), -l(ocals), -i(ivars), -k(klass vars) options to ls
* add -i(nstance) option to show_method
* add --help option to most commands
* Get rid of ls_method and ls_imethods (subsumed by more powerful ls)
* Get rid of show_idoc and show_imethod
* Support YARD and Rdoc documentation
* Add special load-file command that evals target file in current context

View File

@ -1,5 +1,6 @@
direc = File.dirname(__FILE__)
require "#{direc}/command_base"
require "optparse"
class Pry
@ -25,10 +26,12 @@ class Pry
Pry.start(target)
end
command ["exit_program", "quit_program"], "End the current program." do
command "exit-program", "End the current program. Aliases: quit-program" do
exit
end
alias_command "quit-program", "exit-program", ""
command "nesting", "Show nesting information." do
out = output
nesting = opts[:nesting]
@ -65,82 +68,267 @@ class Pry
output.puts "Pry version: #{Pry::VERSION}"
end
command "exit_all", "End all nested Pry sessions." do
command "exit-all", "End all nested Pry sessions." do
throw(:breakout, 0)
end
command "ls", "Show the list of vars in the current scope. Use -c to include constants and -g to include globals." do |*args|
params = []
args.each do |v|
if v[0].chr == "-"
params += v[1..-1].split("")
command "ls", "Show the list of vars in the current scope. Type `ls --help` for more info." do |*args|
options = {}
# Set target local to the default -- note that we can set a different target for
# ls if we like: e.g ls my_var
target = target()
OptionParser.new do |opts|
opts.banner = %{Usage: ls [OPTIONS] [VAR]\n\
List information about VAR (the current context by default).
Always shows local and instance variables by default; use -r to restrict to specific types.
--
}
opts.on("-g", "--globals", "Display global variables.") do |g|
options[:g] = true
end
opts.on("-c", "--constants", "Display constants.") do |c|
options[:c] = true
end
opts.on("-l", "--locals", "Display locals.") do |c|
options[:l] = true
end
opts.on("-i", "--ivars", "Display instance variables.") do |c|
options[:i] = true
end
opts.on("-k", "--class-vars", "Display class variables.") do |c|
options[:k] = true
end
opts.on("-m", "--methods", "Display methods.") do |c|
options[:m] = true
end
opts.on("-M", "--instance-methods", "Display instance methods (only relevant to classes and modules).") do |c|
options[:M] = true
end
opts.on("-s", "--super", "Include superclass entries (relevant to constant and methods options).") do |c|
options[:s] = true
end
opts.on("-r", "--restrict", "Restrict to specified types.") do |c|
options[:r] = true
end
opts.on("-a", "--all", "Display all types of entries.") do |a|
options[:a] = true
end
opts.on("-v", "--verbose", "Verbose ouput.") do |c|
options[:v] = true
end
opts.on_tail("-h", "--help", "Show this message.") do
output.puts opts
options[:h] = true
end
end.order(args) do |new_target|
target = Pry.binding_for(target.eval("#{new_target}")) if !options[:h]
end
# exit if we've displayed help
next if options[:h]
info = {}
target_self = target.eval('self')
extras = []
extras += target.eval("global_variables") if params.include?("g")
# ensure we have a real boolean and not a `nil` (important when
# interpolating in the string)
options[:s] = !!options[:s]
# Numbers (e.g 0, 1, 2) are for ordering the hash values in Ruby
# 1.8
i = -1
info["local variables"] = Array(target.eval("local_variables")).sort, i += 1 if !options[:r] || options[:l] || options[:a]
info["instance variables"] = Array(target.eval("instance_variables")).sort, i += 1 if !options[:r] || options[:i] || options[:a]
info["class variables"] = (target_self.is_a?(Module) ? Array(target.eval("class_variables")).sort : Array(target.eval("self.class.class_variables")).sort), i += 1 if !options[:r] || options[:k] || options[:a]
case target_self
when Module
extras += target.eval("constants") if params.include?("c")
output.puts "#{Pry.view(target.eval("local_variables + instance_variables + #{extras.inspect}"))}"
info["global variables"] = Array(target.eval("global_variables")).sort, i += 1 if options[:g] || options[:a]
info["methods"] = Array(target.eval("methods(#{options[:s]}) + public_methods(#{options[:s]}) +\
protected_methods(#{options[:s]}) +\
private_methods(#{options[:s]})")).uniq.sort, i += 1 if options[:m] || options[:a]
info["instance methods"] = Array(target.eval("instance_methods(#{options[:s]}) +\
public_instance_methods(#{options[:s]}) +\
protected_instance_methods(#{options[:s]}) +\
private_instance_methods(#{options[:s]})")).uniq.sort, i += 1 if target_self.is_a?(Module) && (options[:M] || options[:a])
# dealing with 1.8/1.9 compatibility issues :/
csuper = options[:s]
if Module.method(:constants).arity == 0
csuper = nil
end
info["constants"] = Array(target_self.is_a?(Module) ? target.eval("constants(#{csuper})") :
target.eval("self.class.constants(#{csuper})")).uniq.sort, i += 1 if options[:c] || options[:a]
# verbose output?
if options[:v]
# verbose
info.each.sort_by { |k, v| v.last }.each do |k, v|
if !v.first.empty?
output.puts "#{k}:\n--"
output.puts Pry.view(v.first)
output.puts
end
end
# plain
else
extras += target.eval("self.class.constants") if params.include?("c")
output.puts "#{Pry.view(target.eval("local_variables + instance_variables + #{extras.inspect}"))}"
output.puts Pry.view(info.values.sort_by { |v| v.last }.map { |v| v.first }.inject(&:+))
end
end
command "cat", "Show output of <var>.inspect." do |obj|
out = output
out.puts target.eval("#{obj}.inspect")
command "cat", "Show output of VAR.inspect. Aliases: inspect" do |obj|
if !obj
output.puts "Must provide an object to inspect."
next
end
output.puts target.eval("#{obj}.inspect")
end
alias_command "inspect", "cat", ""
command "cd", "Start a Pry session on <var> (use `cd ..` to go back)" do |obj|
command "cd", "Start a Pry session on VAR (use `cd ..` to go back)" do |obj|
if !obj
output.puts "Must provide an object."
next
end
throw(:breakout, opts[:nesting].level) if obj == ".."
target.eval("#{obj}.pry")
end
command "show_doc", "Show the comments above <methname>" do |meth_name|
doc = target.eval("method(:#{meth_name})").comment
output.puts doc
end
command "show-doc", "Show the comments above METH. Type `show-doc --help` for more info." do |*args|
options = {}
meth_name = nil
OptionParser.new do |opts|
opts.banner = %{Usage: show-doc [OPTIONS] [METH]
Show the comments above method METH. Shows _method_ comments (rather than instance methods) by default.
e.g show-doc hello_method
--
}
opts.on("-M", "--instance-methods", "Operate on instance methods instead.") do |m|
options[:M] = true
end
command "show_idoc", "Show the comments above instance method <methname>" do |meth_name|
doc = target.eval("instance_method(:#{meth_name})").comment
output.puts doc
end
command "show_method", "Show sourcecode for method <methname>." do |meth_name|
meth_name = meth_name_from_binding.call(target) if !meth_name
if meth_name
code = target.eval("method(\"#{meth_name.to_s}\")").source
output.puts code
else
output.puts "Error: Not in a method."
opts.on_tail("-h", "--help", "This message.") do |h|
output.puts opts
options[:h] = true
end
end.order(args) do |v|
meth_name = v
end
next if options[:h]
if !meth_name
output.puts "You need to specify a method. Type `show-doc --help` for help"
next
end
begin
if options[:M]
meth = target.eval("instance_method(:#{meth_name})")
else
meth = target.eval("method(:#{meth_name})")
end
rescue
output.puts "Invalid method name: #{meth_name}. Type `show-doc --help` for help"
next
end
doc = meth.comment
file, line = meth.source_location
output.puts "From #{file} @ line ~#{line}:"
output.puts doc
end
command "show_imethod", "Show sourcecode for instance method <methname>." do |meth_name|
code = target.eval("instance_method(\"#{meth_name}\")").source
command "show-method", "Show the source for METH. Type `show-method --help` for more info." do |*args|
options = {}
meth_name = nil
OptionParser.new do |opts|
opts.banner = %{Usage: show-method [OPTIONS] [METH]
Show the source for method METH. Shows _method_ source (rather than instance methods) by default.
e.g: show-method hello_method
--
}
opts.on("-M", "--instance-methods", "Operate on instance methods instead.") do |m|
options[:M] = true
end
opts.on_tail("-h", "--help", "This message.") do |h|
output.puts opts
options[:h] = true
end
end.order(args) do |v|
meth_name = v
end
next if options[:h]
# If no method name is given then use current method, if it exists
meth_name = meth_name_from_binding.call(target) if !meth_name
if !meth_name
output.puts "You need to specify a method. Type `show-method --help` for help"
next
end
begin
if options[:M]
meth = target.eval("instance_method(:#{meth_name})")
else
meth = target.eval("method(:#{meth_name})")
end
rescue
output.puts "Invalid method name: #{meth_name}. Type `show-method --help` for help"
next
end
code = meth.source
file, line = meth.source_location
output.puts "From #{file} @ line #{line}:"
output.puts code
end
command "show_command", "Show sourcecode for a Pry command, e.g: show_command ls" do |command_name|
command "show-command", "Show sourcecode for a Pry command, e.g: show-command ls" do |command_name|
cmds = Pry.active_instance.commands.commands
if !command_name
output.puts "You must provide a command name."
next
end
if cmds[command_name]
code = cmds[command_name][:action].source
meth = cmds[command_name][:action]
code = meth.source
file, line = meth.source_location
output.puts "From #{file} @ line #{line}:"
output.puts code
else
output.puts "No such command: #{command_name}."
end
end
command "jump_to", "Jump to a Pry session further up the stack, exiting all sessions below." do |break_level|
command "jump-to", "Jump to a Pry session further up the stack, exiting all sessions below." do |break_level|
break_level = break_level.to_i
nesting = opts[:nesting]
@ -155,16 +343,11 @@ class Pry
end
end
command "ls_methods", "List all methods defined on class of receiver." do
output.puts "#{Pry.view(target.eval('(public_methods(false) + private_methods(false) + protected_methods(false)).sort'))}"
end
command "ls_imethods", "List all instance methods defined on class of receiver." do
output.puts "#{Pry.view(target.eval('(public_instance_methods(false) + private_instance_methods(false) + protected_instance_methods(false)).sort'))}"
end
command ["exit", "quit", "back"], "End the current Pry session." do
command "exit", "End the current Pry session. Aliases: quit, back" do
throw(:breakout, opts[:nesting].level)
end
alias_command "quit", "exit", ""
alias_command "back", "exit", ""
end
end

View File

@ -117,4 +117,21 @@ class Pry
# last element in nesting array is the pry instance
nesting.map(&:last)
end
# Return a `Binding` object for `target` or return `target` if it is
# already a `Binding`.
# In the case where `target` is top-level then return `TOPLEVEL_BINDING`
# @param [Object] target The object to get a `Binding` object for.
# @return [Binding] The `Binding` object.
def self.binding_for(target)
if target.is_a?(Binding)
target
else
if target == TOPLEVEL_BINDING.eval('self')
TOPLEVEL_BINDING
else
target.__binding__
end
end
end
end

View File

@ -71,7 +71,7 @@ class Pry
# @example
# Pry.new.repl(Object.new)
def repl(target=TOPLEVEL_BINDING)
target = binding_for(target)
target = Pry.binding_for(target)
target_self = target.eval('self')
exec_hook :before_session, output, target_self
@ -111,7 +111,7 @@ class Pry
# @example
# Pry.new.rep(Object.new)
def rep(target=TOPLEVEL_BINDING)
target = binding_for(target)
target = Pry.binding_for(target)
print.call output, re(target)
end
@ -122,7 +122,7 @@ class Pry
# @example
# Pry.new.re(Object.new)
def re(target=TOPLEVEL_BINDING)
target = binding_for(target)
target = Pry.binding_for(target)
if input == Readline
# Readline tab completion
@ -154,13 +154,17 @@ class Pry
# @example
# Pry.new.r(Object.new)
def r(target=TOPLEVEL_BINDING)
target = binding_for(target)
target = Pry.binding_for(target)
eval_string = ""
loop do
current_prompt = select_prompt(eval_string.empty?, target.eval('self'))
val = readline(current_prompt)
# exit pry if we receive EOF character
val = "quit" if !val
val.chomp!
process_commands(val, eval_string, target)
@ -289,21 +293,4 @@ class Pry
true
end
end
# Return a `Binding` object for `target` or return `target` if it is
# already a `Binding`.
# In the case where `target` is top-level then return `TOPLEVEL_BINDING`
# @param [Object] target The object to get a `Binding` object for.
# @return [Binding] The `Binding` object.
def binding_for(target)
if target.is_a?(Binding)
target
else
if target == TOPLEVEL_BINDING.eval('self')
TOPLEVEL_BINDING
else
target.__binding__
end
end
end
end

View File

@ -1,3 +1,3 @@
class Pry
VERSION = "0.4.9pre1"
VERSION = "0.5.0pre2"
end

View File

@ -200,12 +200,12 @@ describe Pry do
end
end
describe "Pry#binding_for" do
describe "Pry.binding_for" do
it 'should return TOPLEVEL_BINDING if parameter self is main' do
_main_ = lambda { TOPLEVEL_BINDING.eval('self') }
Pry.new.binding_for(_main_.call).is_a?(Binding).should == true
Pry.new.binding_for(_main_.call).should == TOPLEVEL_BINDING
Pry.new.binding_for(_main_.call).should == Pry.new.binding_for(_main_.call)
Pry.binding_for(_main_.call).is_a?(Binding).should == true
Pry.binding_for(_main_.call).should == TOPLEVEL_BINDING
Pry.binding_for(_main_.call).should == Pry.binding_for(_main_.call)
end
end
@ -346,7 +346,7 @@ describe Pry do
end
Command3.commands.include?("nesting").should == true
Command3.commands.include?("jump_to").should == true
Command3.commands.include?("jump-to").should == true
Command3.commands.include?("cd").should == true
Command3.commands.include?("v").should == true
@ -422,7 +422,7 @@ describe Pry do
it 'should import commands from another command object' do
class Command3 < Pry::CommandBase
import_from Pry::Commands, "status", "jump_to"
import_from Pry::Commands, "status", "jump-to"
end
str_output = StringIO.new
@ -443,7 +443,7 @@ describe Pry do
end
Command3.commands.include?("nesting").should == true
Command3.commands.include?("jump_to").should == true
Command3.commands.include?("jump-to").should == true
Command3.commands.include?("cd").should == true
Command3.commands.include?("v").should == true
Command3.commands.include?("show_doc").should == false
@ -455,8 +455,8 @@ describe Pry do
it 'should override some inherited commands' do
class Command3 < Pry::Commands
command "jump_to" do
output.puts "jump_to the music"
command "jump-to" do
output.puts "jump-to the music"
end
command "help" do
@ -468,8 +468,8 @@ describe Pry do
Pry.print = proc {}
str_output = StringIO.new
Pry.new(:input => InputTester.new("jump_to"), :output => str_output, :commands => Command3).rep
str_output.string.chomp.should == "jump_to the music"
Pry.new(:input => InputTester.new("jump-to"), :output => str_output, :commands => Command3).rep
str_output.string.chomp.should == "jump-to the music"
str_output = StringIO.new
Pry.new(:input => InputTester.new("help"), :output => str_output, :commands => Command3).rep