Merge branch 'classify-commands'

This commit is contained in:
Conrad Irwin 2011-12-31 15:32:46 +00:00
commit 80d50c9bb4
16 changed files with 844 additions and 663 deletions

View File

@ -184,9 +184,9 @@ require "pry/wrapped_module"
require "pry/history_array"
require "pry/helpers"
require "pry/history"
require "pry/command"
require "pry/command_set"
require "pry/commands"
require "pry/command_context"
require "pry/custom_completions"
require "pry/completion"
require "pry/plugins"

296
lib/pry/command.rb Normal file
View File

@ -0,0 +1,296 @@
class Pry
# The super-class of all commands, new commands should be created by calling
# {Pry::CommandSet#command} which creates a BlockCommand or {Pry::CommandSet#command_class}
# which creates a ClassCommand. Please don't use this class directly.
class Command
# represents a void return value for a command
VOID_VALUE = Object.new
# give it a nice inspect
def VOID_VALUE.inspect() "void" end
# Properties of the command itself (as passed as arguments to
# {CommandSet#command} or {CommandSet#command_class}).
class << self
attr_accessor :name
attr_accessor :description
attr_accessor :options
attr_accessor :block
end
# Make those properties accessible to instances
def name; self.class.name; end
def description; self.class.description; end
def block; self.class.block; end
def command_options; self.class.options; end
def command_name; command_options[:listing]; end
class << self
def inspect
"#<class(Pry::Command #{name.inspect})>"
end
# Create a new command with the given properties.
#
# @param String name the name of the command
# @param String description the description to appear in {help}
# @param Hash options behavioural options (@see {Pry::CommandSet#command})
# @param Module helpers a module of helper functions to be included.
# @param Proc &block (optional, a block, used for BlockCommands)
#
# @return Class (a subclass of Pry::Command)
#
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
# Store hooks to be run before or after the command body.
# @see {Pry::CommandSet#before_command}
# @see {Pry::CommandSet#after_command}
def hooks
@hooks ||= {:before => [], :after => []}
end
end
# Properties of one execution of a command (passed by the {Pry::CommandProcessor} as a hash of
# context and expanded in {#initialize}
attr_accessor :output
attr_accessor :target
attr_accessor :captures
attr_accessor :eval_string
attr_accessor :arg_string
attr_accessor :context
attr_accessor :command_set
attr_accessor :command_processor
attr_accessor :_pry_
# 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
# @example
# run "show-input"
# @example
# run ".ls"
# @example
# run "amend-line", "5", 'puts "hello world"'
def run(command_string, *args)
complete_string = "#{command_string} #{args.join(" ")}"
command_processor.process_commands(complete_string, eval_string, target)
end
def commands
command_set.commands
end
def text
Pry::Helpers::Text
end
def void
VOID_VALUE
end
include Pry::Helpers::BaseHelpers
include Pry::Helpers::CommandHelpers
# Instantiate a command, in preparation for calling it.
#
# @param Hash context The runtime context to use with this command.
def initialize(context={})
self.context = context
self.target = context[:target]
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._pry_ = context[:pry_instance]
self.command_processor = context[:command_processor]
end
# The value of {self} inside the {target} binding.
def target_self; target.eval('self'); end
# Run the command with the given {args}.
#
# This is a public wrapper around {#call} which ensures all preconditions are met.
#
# @param *[String] the arguments to pass to this command.
# @return Object the return value of the {#call} method, or Command::VOID_VALUE
def call_safely(*args)
unless dependencies_met?
gems_needed = Array(command_options[:requires_gem])
gems_not_installed = gems_needed.select { |g| !gem_installed?(g) }
output.puts "\nThe command '#{name}' is #{Helpers::Text.bold("unavailable")} because it requires the following gems to be installed: #{(gems_not_installed.join(", "))}"
output.puts "-"
output.puts "Type `install-command #{name}` to install the required gems and activate this command."
return void
end
if command_options[:argument_required] && args.empty?
raise CommandError, "The command '#{name}' requires an argument."
end
ret = call_with_hooks(*args)
command_options[:keep_retval] ? ret : void
end
# Are all the gems required to use this command installed?
#
# @return Boolean
def dependencies_met?
@dependencies_met ||= command_dependencies_met?(command_options)
end
private
# Run the {#call} method and all the registered hooks.
#
# @param *String the arguments to #{call}
# @return Object the return value from #{call}
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
ret
end
end
# A super-class for Commands that are created with a single block.
#
# This class ensures that the block is called with the correct number of arguments
# and the right context.
#
# Create subclasses using {Pry::CommandSet#command}.
class BlockCommand < Command
# backwards compatibility
alias_method :opts, :context
def call(*args)
instance_exec(*correct_arg_arity(block.arity, args), &block)
end
def correct_arg_arity(arity, args)
case
when arity < 0
args
when arity == 0
[]
when arity > 0
args.values_at *(0..(arity - 1)).to_a
end
end
def help; description; end
end
# A super-class ofr Commands with structure.
#
# This class implements the bare-minimum functionality that a command should have,
# namely a --help switch, and then delegates actual processing to its subclasses.
#
# Create subclasses using {Pry::CommandSet#command_class}, and override the {options(opt)} method
# to set up an instance of Slop, and the {process} method to actually run the command. If
# necessary, you can also override {setup} which will be called before {options}, for example to
# require any gems your command needs to run, or to set up state.
class ClassCommand < Command
attr_accessor :opts
attr_accessor :args
# Set up {opts} and {args}, and then call {process}
#
# This function will display help if necessary.
#
# @param *String the arguments passed
# @return Object the return value of {process} or VOID_VALUE
def call(*args)
setup
self.opts = slop
self.args = self.opts.parse!(args)
if opts.present?(:help)
output.puts slop.help
void
else
process
end
end
# Return the help generated by Slop for this command.
def help
slop.help
end
# Return an instance of Slop that can parse the options that this command accepts.
def slop
Slop.new do |opt|
options(opt)
opt.on(:h, :help, "Show this message.")
end
end
# A function called just before {options(opt)} as part of {call}.
#
# This function can be used to set up any context your command needs to run, for example
# requiring gems, or setting default values for options.
#
# @example
# def setup;
# require 'gist'
# @action = :method
# end
def setup; end
# A function to setup Slop so it can parse the options your command expects.
#
# NOTE: please don't do anything side-effecty in the main part of this method,
# as it may be called by Pry at any time for introspection reasons. If you need
# to set up default values, use {setup} instead.
#
# @example
# def options(opt)
# opt.banner "Gists methods or classes"
# opt.on(:c, :class, "gist a class") do
# @action = :class
# end
# end
def options(opt); end
# The actual body of your command should go here.
#
# The {opts} mehod can be called to get the options that Slop has passed,
# and {args} gives the remaining, unparsed arguments.
#
# The return value of this method is discarded unless the command was created
# with :keep_retval => true, in which case it is returned to the repl.
#
# @example
# def process
# if opts.present?(:class)
# gist_class
# else
# gist_method
# end
# end
def process; raise CommandError, "command '#{name}' not implemented" end
end
end

View File

@ -1,57 +0,0 @@
class Pry
# Command contexts are the objects runing each command.
# Helper modules can be mixed into this class.
class CommandContext
# represents a void return value for a command
VOID_VALUE = Object.new
# give it a nice inspect
def VOID_VALUE.inspect() "void" end
attr_accessor :command_name
attr_accessor :output
attr_accessor :target
attr_accessor :target_self
attr_accessor :captures
attr_accessor :eval_string
attr_accessor :arg_string
attr_accessor :opts
attr_accessor :command_set
attr_accessor :command_processor
attr_accessor :_pry_
def initialize(&block)
instance_exec(&block) if block
end
# 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
# @example
# run "show-input"
# @example
# run ".ls"
# @example
# run "amend-line", "5", 'puts "hello world"'
def run(command_string, *args)
complete_string = "#{command_string} #{args.join(" ")}"
command_processor.process_commands(complete_string, eval_string, target)
end
def commands
command_set.commands
end
def text
Pry::Helpers::Text
end
def void
VOID_VALUE
end
include Pry::Helpers::BaseHelpers
include Pry::Helpers::CommandHelpers
end
end

View File

@ -22,7 +22,7 @@ class Pry
# (one that does not return a value)
# @return [Boolean]
def void_command?
(command? && !keep_retval?) || retval == CommandContext::VOID_VALUE
(command? && !keep_retval?) || retval == Command::VOID_VALUE
end
# Is the return value kept for this command? (i.e :keep_retval => true)
@ -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,43 +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
if command.callable.is_a?(Proc)
context = CommandContext.new
else
# in the case of non-procs the callable *is* the context
context = command.callable
end
# set some useful methods to be used by the action blocks
setup_context(target, command, context, options)
instance = command.new(context)
catch(:command_done) do
ret = commands.run_command(context, command.name, *args)
ret = instance.call_safely(*args)
end
options[:val].replace("")
# FIXME: wtf?
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

View File

@ -8,42 +8,6 @@ class Pry
# This class is used to create sets of commands. Commands can be imported from
# different sets, aliased, removed, etc.
class CommandSet
class Command < Struct.new(:name, :description, :options, :callable)
def call(context, *args)
if stub_block = options[:stub_info]
context.instance_eval(&stub_block)
else
if callable.is_a?(Proc)
ret = context.instance_exec(*correct_arg_arity(callable.arity, args), &callable)
else
# in the case of non-procs the callable *is* the context
ret = callable.call(*correct_arg_arity(callable.method(:call).arity, args))
end
if options[:keep_retval]
ret
else
Pry::CommandContext::VOID_VALUE
end
end
end
private
def correct_arg_arity(arity, args)
case
when arity < 0
args
when arity == 0
[]
when arity > 0
args.values_at *(0..(arity - 1)).to_a
end
end
end
include Enumerable
include Pry::Helpers::BaseHelpers
@ -118,29 +82,42 @@ class Pry
# # pry(main)> help number
# # number-N regex command
def command(name, description="No description.", options={}, &block)
options = default_options(name).merge!(options)
options = {
:requires_gem => [],
:keep_retval => false,
:argument_required => false,
:interpolate => true,
:shellwords => true,
:listing => name,
:use_prefix => true
}.merge!(options)
commands[name] = Pry::BlockCommand.subclass(name, description, options, helper_module, &block)
end
unless command_dependencies_met? options
gems_needed = Array(options[:requires_gem])
gems_not_installed = gems_needed.select { |g| !gem_installed?(g) }
# Defines a new Pry command class.
#
# @param [String, Regexp] name The name of the command. Can be
# Regexp as well as String.
# @param [String] description A description of the command.
# @param [Hash] options The optional configuration parameters, see {#command}
# @param &Block The class body's definition.
#
# @example
# Pry::Commands.command_class "echo", "echo's the input", :shellwords => false do
# def options(opt)
# opt.banner "Usage: echo [-u | -d] <string to echo>"
# opt.on :u, :upcase, "ensure the output is all upper-case"
# opt.on :d, :downcase, "ensure the output is all lower-case"
# end
#
# def process
# raise Pry::CommandError, "-u and -d makes no sense" if opts.present?(:u) && opts.present?(:d)
# result = args.join(" ")
# result.downcase! if opts.present?(:downcase)
# result.upcase! if opts.present?(:upcase)
# output.puts result
# end
# end
#
def command_class(name, description="No description.", options={}, &block)
options = default_options(name).merge!(options)
options[:stub_info] = proc do
output.puts "\nThe command '#{name}' is #{Helpers::Text.bold("unavailable")} because it requires the following gems to be installed: #{(gems_not_installed.join(", "))}"
output.puts "-"
output.puts "Type `install-command #{name}` to install the required gems and activate this command."
end
end
commands[name] = Command.new(name, description, options, options[:definition] ? options.delete(:definition) : block)
commands[name] = Pry::ClassCommand.subclass(name, description, options, helper_module)
commands[name].class_eval(&block)
commands[name]
end
# Execute a block of code before a command is invoked. The block also
@ -154,18 +131,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
@ -179,18 +145,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
@ -278,29 +233,6 @@ class Pry
commands.delete(cmd.name)
end
# Runs a command.
# @param [Object] context Object which will be used as self during the
# command.
# @param [String] name Name of the command to be run
# @param [Array<Object>] args Arguments passed to the command
# @raise [NoCommandError] If the command is not defined in this set
def run_command(context, command_name, *args)
command = commands[command_name]
context.extend helper_module
if command.nil?
raise NoCommandError.new(command_name, self)
end
if command.options[:argument_required] && args.empty?
puts "The command '#{command.name}' requires an argument."
else
command.call context, *args
end
end
# Sets or gets the description for a command (replacing the old
# description). Returns current description if no description
# parameter provided.
@ -341,7 +273,26 @@ class Pry
commands.keys
end
# @nodoc used for testing
def run_command(context, name, *args)
command = commands[name] or raise NoCommandError.new(name, self)
command.new(context).call_safely(*args)
end
private
def default_options(name)
{
:requires_gem => [],
:keep_retval => false,
:argument_required => false,
:interpolate => true,
:shellwords => true,
:listing => name,
:use_prefix => true
}
end
def define_default_commands
command "help", "This menu." do |cmd|
@ -358,7 +309,7 @@ class Pry
stagger_output(help_text)
else
if command = find_command(cmd)
output.puts command.description
output.puts command.new.help
else
output.puts "No info for command: #{cmd}"
end
@ -368,10 +319,9 @@ class Pry
command "install-command", "Install a disabled command." do |name|
require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller
command = find_command(name)
stub_info = command.options[:stub_info]
if !stub_info
output.puts "Not a command stub. Nothing to do."
if command_dependencies_met?(command.options)
output.puts "Dependencies for #{command.name} are met. Nothing to do."
next
end
@ -404,7 +354,6 @@ class Pry
end
next if gem_install_failed
command.options.delete :stub_info
output.puts "Installation of `#{name}` successful! Type `help #{name}` for information"
end
end

View File

@ -55,153 +55,141 @@ class Pry
EOS
end
require 'pry/command_context'
command_class "gist", "Gist a method or expression history to github. Type `gist --help` for more info.", :requires_gem => "gist", :shellwords => false do
attr_accessor :content
attr_accessor :code_type
attr_accessor :input_ranges
command("gist", "Gist a method or expression history to github. Type `gist --help` for more info.",{:requires_gem => "gist", :shellwords => false, :definition => Pry::CommandContext.new{
def setup
require 'gist'
end
class << self
attr_accessor :opts
attr_accessor :content
attr_accessor :code_type
attr_accessor :input_ranges
end
def options(opt)
opt.banner unindent <<-USAGE
Usage: gist [OPTIONS] [METH]
Gist method (doc or source) or input expression to github.
Ensure the `gist` gem is properly working before use. http://github.com/defunkt/gist for instructions.
e.g: gist -m my_method
e.g: gist -d my_method
e.g: gist -i 1..10
USAGE
def call(*args)
require 'gist'
opt.on :d, :doc, "Gist a method's documentation.", true
opt.on :m, :method, "Gist a method's source.", true
opt.on :f, :file, "Gist a file.", true
opt.on :p, :public, "Create a public gist (default: false)", :default => false
opt.on :l, :lines, "Only gist a subset of lines (only works with -m and -f)", :optional => true, :as => Range, :default => 1..-1
opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional => true,
:as => Range, :default => -5..-1 do |range|
input_ranges << absolute_index_range(range, _pry_.input_array.length)
end
end
gather_options(args)
process_options
perform_gist
end
def process
if opts.present?(:in)
in_option
elsif opts.present?(:file)
file_option
elsif opts.present?(:doc)
doc_option
elsif opts.present?(:method)
method_option
end
def gather_options(args)
self.input_ranges = []
perform_gist
end
self.opts = parse_options!(args) do |opt|
opt.banner unindent <<-USAGE
Usage: gist [OPTIONS] [METH]
Gist method (doc or source) or input expression to github.
Ensure the `gist` gem is properly working before use. http://github.com/defunkt/gist for instructions.
e.g: gist -m my_method
e.g: gist -d my_method
e.g: gist -i 1..10
USAGE
def in_option
self.code_type = :ruby
self.content = ""
opt.on :d, :doc, "Gist a method's documentation.", true
opt.on :m, :method, "Gist a method's source.", true
opt.on :f, :file, "Gist a file.", true
opt.on :p, :public, "Create a public gist (default: false)", :default => false
opt.on :l, :lines, "Only gist a subset of lines (only works with -m and -f)", :optional => true, :as => Range, :default => 1..-1
opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional => true,
:as => Range, :default => -5..-1 do |range|
input_ranges << absolute_index_range(range, _pry_.input_array.length)
end
end
end
def process_options
if opts.present?(:in)
in_option
elsif opts.present?(:file)
file_option
elsif opts.present?(:doc)
doc_option
elsif opts.present?(:method)
method_option
end
end
def in_option
self.code_type = :ruby
self.content = ""
input_ranges.each do |range|
input_expressions = _pry_.input_array[range] || []
input_expressions.each_with_index.map do |code, index|
corrected_index = index + range.first
if code && code != ""
self.content << code
if code !~ /;\Z/
self.content << "#{comment_expression_result_for_gist(Pry.config.gist.inspecter.call(_pry_.output_array[corrected_index]))}"
end
end
end
end
end
def file_option
whole_file = File.read(File.expand_path(opts[:f]))
if opts.present?(:lines)
self.content = restrict_to_lines(whole_file, opts[:l])
else
self.content = whole_file
end
end
def doc_option
meth = get_method_or_raise(opts[:d], target, {})
self.content = meth.doc
self.code_type = meth.source_type
text.no_color do
self.content = process_comment_markup(self.content, self.code_type)
end
self.code_type = :plain
end
def method_option
meth = get_method_or_raise(opts[:m], target, {})
method_source = meth.source
if opts.present?(:lines)
self.content = restrict_to_lines(method_source, opts[:l])
else
self.content = method_source
end
self.code_type = meth.source_type
end
def perform_gist
type_map = { :ruby => "rb", :c => "c", :plain => "plain" }
# prevent Gist from exiting the session on error
begin
extname = opts.present?(:file) ? ".#{gist_file_extension(opts[:f])}" : ".#{type_map[self.code_type]}"
link = Gist.write([:extension => extname,
:input => self.content],
!opts[:p])
rescue SystemExit
end
if link
Gist.copy(link)
output.puts "Gist created at #{link} and added to clipboard."
end
end
def restrict_to_lines(content, lines)
line_range = one_index_range(lines)
content.lines.to_a[line_range].join
end
def gist_file_extension(file_name)
file_name.split(".").last
end
def comment_expression_result_for_gist(result)
content = ""
result.lines.each_with_index do |line, index|
if index == 0
content << "# => #{line}"
else
content << "# #{line}"
end
end
content
end
}})
input_ranges.each do |range|
input_expressions = _pry_.input_array[range] || []
input_expressions.each_with_index.map do |code, index|
corrected_index = index + range.first
if code && code != ""
self.content << code
if code !~ /;\Z/
self.content << "#{comment_expression_result_for_gist(Pry.config.gist.inspecter.call(_pry_.output_array[corrected_index]))}"
end
end
end
end
end
def file_option
whole_file = File.read(File.expand_path(opts[:f]))
if opts.present?(:lines)
self.content = restrict_to_lines(whole_file, opts[:l])
else
self.content = whole_file
end
end
def doc_option
meth = get_method_or_raise(opts[:d], target, {})
self.content = meth.doc
self.code_type = meth.source_type
text.no_color do
self.content = process_comment_markup(self.content, self.code_type)
end
self.code_type = :plain
end
def method_option
meth = get_method_or_raise(opts[:m], target, {})
method_source = meth.source
if opts.present?(:lines)
self.content = restrict_to_lines(method_source, opts[:l])
else
self.content = method_source
end
self.code_type = meth.source_type
end
def perform_gist
type_map = { :ruby => "rb", :c => "c", :plain => "plain" }
# prevent Gist from exiting the session on error
begin
extname = opts.present?(:file) ? ".#{gist_file_extension(opts[:f])}" : ".#{type_map[self.code_type]}"
link = Gist.write([:extension => extname,
:input => self.content],
!opts[:p])
rescue SystemExit
end
if link
Gist.copy(link)
output.puts "Gist created at #{link} and added to clipboard."
end
end
def restrict_to_lines(content, lines)
line_range = one_index_range(lines)
content.lines.to_a[line_range].join
end
def gist_file_extension(file_name)
file_name.split(".").last
end
def comment_expression_result_for_gist(result)
content = ""
result.lines.each_with_index do |line, index|
if index == 0
content << "# => #{line}"
else
content << "# #{line}"
end
end
content
end
end
end
end
end

View File

@ -69,7 +69,7 @@ class Pry
end
if find_command(command_name)
block = Pry::Method.new(find_command(command_name).callable)
block = Pry::Method.new(find_command(command_name).block)
next unless block.source
set_file_and_dir_locals(block.source_file)

View File

@ -3,7 +3,96 @@ class Pry
Ls = Pry::CommandSet.new do
helpers do
command_class "ls","Show the list of vars and methods in the current scope. Type `ls --help` for more info.",
:shellwords => false, :interpolate => false do
def options(opt)
opt.banner unindent <<-USAGE
Usage: ls [-m|-M|-p|-pM] [-q|-v] [-c|-i] [Object]
ls [-g] [-l]
ls shows you which methods, constants and variables are accessible to Pry. By default it shows you the local variables defined in the current shell, and any public methods or instance variables defined on the current object.
The colours used are configurable using Pry.config.ls.*_color, and the separator is Pry.config.ls.separator.
Pry.config.ls.ceiling is used to hide methods defined higher up in the inheritance chain, this is by default set to [Object, Module, Class] so that methods defined on all Objects are omitted. The -v flag can be used to ignore this setting and show all methods, while the -q can be used to set the ceiling much lower and show only methods defined on the object or its direct class.
USAGE
opt.on :m, "methods", "Show public methods defined on the Object (default)"
opt.on :M, "instance-methods", "Show methods defined in a Module or Class"
opt.on :p, "ppp", "Show public, protected (in yellow) and private (in green) methods"
opt.on :q, "quiet", "Show only methods defined on object.singleton_class and object.class"
opt.on :v, "verbose", "Show methods and constants on all super-classes (ignores Pry.config.ls.ceiling)"
opt.on :g, "globals", "Show global variables, including those builtin to Ruby (in cyan)"
opt.on :l, "locals", "Show locals, including those provided by Pry (in red)"
opt.on :c, "constants", "Show constants, highlighting classes (in blue), and exceptions (in purple)"
opt.on :i, "ivars", "Show instance variables (in blue) and class variables (in bright blue)"
opt.on :G, "grep", "Filter output by regular expression", :optional => false
opt.on :h, "help", "Show help"
end
def process
obj = args.empty? ? target_self : target.eval(args.join(" "))
# exclude -q, -v and --grep because they don't specify what the user wants to see.
has_opts = (opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) ||
opts.present?(:globals) || opts.present?(:locals) || opts.present?(:constants) ||
opts.present?(:ivars))
show_methods = opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) || !has_opts
show_constants = opts.present?(:constants) || (!has_opts && Module === obj)
show_ivars = opts.present?(:ivars) || !has_opts
show_locals = opts.present?(:locals) || (!has_opts && args.empty?)
grep_regex, grep = [Regexp.new(opts[:G] || "."), lambda{ |x| x.grep(grep_regex) }]
raise Pry::CommandError, "-l does not make sense with a specified Object" if opts.present?(:locals) && !args.empty?
raise Pry::CommandError, "-g does not make sense with a specified Object" if opts.present?(:globals) && !args.empty?
raise Pry::CommandError, "-q does not make sense with -v" if opts.present?(:quiet) && opts.present?(:verbose)
raise Pry::CommandError, "-M only makes sense with a Module or a Class" if opts.present?(:'instance-methods') && !(Module === obj)
raise Pry::CommandError, "-c only makes sense with a Module or a Class" if opts.present?(:constants) && !args.empty? && !(Module === obj)
if opts.present?(:globals)
output_section("global variables", grep[format_globals(target.eval("global_variables"))])
end
if show_constants
mod = Module === obj ? obj : Object
constants = mod.constants
constants -= (mod.ancestors - [mod]).map(&:constants).flatten unless opts.present?(:verbose)
output_section("constants", grep[format_constants(mod, constants)])
end
if show_methods
# methods is a hash {Module/Class => [Pry::Methods]}
methods = all_methods(obj).select{ |method| opts.present?(:ppp) || method.visibility == :public }.group_by(&:owner)
# reverse the resolution order so that the most useful information appears right by the prompt
resolution_order(obj).take_while(&below_ceiling(obj)).reverse.each do |klass|
methods_here = format_methods((methods[klass] || []).select{ |m| m.name =~ grep_regex })
output_section "#{Pry::WrappedModule.new(klass).method_prefix}methods", methods_here
end
end
if show_ivars
klass = (Module === obj ? obj : obj.class)
output_section("instance variables", format_variables(:instance_var, grep[obj.__send__(:instance_variables)]))
output_section("class variables", format_variables(:class_var, grep[klass.__send__(:class_variables)]))
end
if show_locals
output_section("locals", format_locals(grep[target.eval("local_variables")]))
end
end
private
# http://ruby.runpaint.org/globals, and running "puts global_variables.inspect".
BUILTIN_GLOBALS = %w($" $$ $* $, $-0 $-F $-I $-K $-W $-a $-d $-i $-l $-p $-v $-w $. $/ $\\
@ -20,17 +109,17 @@ class Pry
$LAST_PAREN_MATCH $LAST_READ_LINE $MATCH $POSTMATCH $PREMATCH)
# Get all the methods that we'll want to output
def all_methods(obj, opts)
def all_methods(obj)
opts.present?(:'instance-methods') ? Pry::Method.all_from_class(obj) : Pry::Method.all_from_obj(obj)
end
def resolution_order(obj, opts)
def resolution_order(obj)
opts.present?(:'instance-methods') ? Pry::Method.instance_resolution_order(obj) : Pry::Method.resolution_order(obj)
end
# Get a lambda that can be used with .take_while to prevent over-eager
# traversal of the Object's ancestry graph.
def below_ceiling(obj, opts)
def below_ceiling(obj)
ceiling = if opts.present?(:quiet)
[opts.present?(:'instance-methods') ? obj.ancestors[1] : obj.class.ancestors[1]] + Pry.config.ls.ceiling
elsif opts.present?(:verbose)
@ -108,96 +197,6 @@ class Pry
text.send(Pry.config.ls.send(:"#{type}_color"), str)
end
end
command "ls", "Show the list of vars and methods in the current scope. Type `ls --help` for more info.",
:shellwords => false, :interpolate => false do |*args|
opts = Slop.parse!(args, :strict => true) do |opt|
opt.banner unindent <<-USAGE
Usage: ls [-m|-M|-p|-pM] [-q|-v] [-c|-i] [Object]
ls [-g] [-l]
ls shows you which methods, constants and variables are accessible to Pry. By default it shows you the local variables defined in the current shell, and any public methods or instance variables defined on the current object.
The colours used are configurable using Pry.config.ls.*_color, and the separator is Pry.config.ls.separator.
Pry.config.ls.ceiling is used to hide methods defined higher up in the inheritance chain, this is by default set to [Object, Module, Class] so that methods defined on all Objects are omitted. The -v flag can be used to ignore this setting and show all methods, while the -q can be used to set the ceiling much lower and show only methods defined on the object or its direct class.
USAGE
opt.on :m, "methods", "Show public methods defined on the Object (default)"
opt.on :M, "instance-methods", "Show methods defined in a Module or Class"
opt.on :p, "ppp", "Show public, protected (in yellow) and private (in green) methods"
opt.on :q, "quiet", "Show only methods defined on object.singleton_class and object.class"
opt.on :v, "verbose", "Show methods and constants on all super-classes (ignores Pry.config.ls.ceiling)"
opt.on :g, "globals", "Show global variables, including those builtin to Ruby (in cyan)"
opt.on :l, "locals", "Show locals, including those provided by Pry (in red)"
opt.on :c, "constants", "Show constants, highlighting classes (in blue), and exceptions (in purple)"
opt.on :i, "ivars", "Show instance variables (in blue) and class variables (in bright blue)"
opt.on :G, "grep", "Filter output by regular expression", :optional => false
opt.on :h, "help", "Show help"
end
next output.puts(opts.to_s) if opts.present?(:help)
obj = args.empty? ? target_self : target.eval(args.join(" "))
# exclude -q, -v and --grep because they don't specify what the user wants to see.
has_opts = (opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) ||
opts.present?(:globals) || opts.present?(:locals) || opts.present?(:constants) ||
opts.present?(:ivars))
show_methods = opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) || !has_opts
show_constants = opts.present?(:constants) || (!has_opts && Module === obj)
show_ivars = opts.present?(:ivars) || !has_opts
show_locals = opts.present?(:locals) || (!has_opts && args.empty?)
grep_regex, grep = [Regexp.new(opts[:G] || "."), lambda{ |x| x.grep(grep_regex) }]
raise Pry::CommandError, "-l does not make sense with a specified Object" if opts.present?(:locals) && !args.empty?
raise Pry::CommandError, "-g does not make sense with a specified Object" if opts.present?(:globals) && !args.empty?
raise Pry::CommandError, "-q does not make sense with -v" if opts.present?(:quiet) && opts.present?(:verbose)
raise Pry::CommandError, "-M only makes sense with a Module or a Class" if opts.present?(:'instance-methods') && !(Module === obj)
raise Pry::CommandError, "-c only makes sense with a Module or a Class" if opts.present?(:constants) && !args.empty? && !(Module === obj)
if opts.present?(:globals)
output_section("global variables", grep[format_globals(target.eval("global_variables"))])
end
if show_constants
mod = Module === obj ? obj : Object
constants = mod.constants
constants -= (mod.ancestors - [mod]).map(&:constants).flatten unless opts.present?(:verbose)
output_section("constants", grep[format_constants(mod, constants)])
end
if show_methods
# methods is a hash {Module/Class => [Pry::Methods]}
methods = all_methods(obj, opts).select{ |method| opts.present?(:ppp) || method.visibility == :public }.group_by(&:owner)
# reverse the resolution order so that the most useful information appears right by the prompt
resolution_order(obj, opts).take_while(&below_ceiling(obj, opts)).reverse.each do |klass|
methods_here = format_methods((methods[klass] || []).select{ |m| m.name =~ grep_regex })
output_section "#{Pry::WrappedModule.new(klass).method_prefix}methods", methods_here
end
end
if show_ivars
klass = (Module === obj ? obj : obj.class)
output_section("instance variables", format_variables(:instance_var, grep[obj.__send__(:instance_variables)]))
output_section("class variables", format_variables(:class_var, grep[klass.__send__(:class_variables)]))
end
if show_locals
output_section("locals", format_locals(grep[target.eval("local_variables")]))
end
end
end
end
end

View File

@ -1,7 +1,7 @@
class Pry
module Helpers
# The methods defined on {Text} are available to custom commands via {Pry::CommandContext#text}.
# The methods defined on {Text} are available to custom commands via {Pry::Command#text}.
module Text
COLORS =

View File

@ -378,12 +378,12 @@ class Pry
# @param [String] val The command (and its params) to execute.
# @param [String] eval_string The current input buffer.
# @param [Binding] target The binding to use..
# @return [Pry::CommandContext::VOID_VALUE]
# @return [Pry::Command::VOID_VALUE]
# @example
# pry_instance.run_command("ls -m")
def run_command(val, eval_string = "", target = binding_stack.last)
@command_processor.process_commands(val, eval_string, target)
Pry::CommandContext::VOID_VALUE
Pry::Command::VOID_VALUE
end
# Set the last result of an eval.

View File

@ -98,6 +98,12 @@ def mock_pry(*args)
output.string
end
def mock_command(cmd, args=[], opts={})
output = StringIO.new
ret = cmd.new(opts.merge(:output => output)).call_safely(*args)
Struct.new(:output, :return).new(output.string, ret)
end
def redirect_global_pry_input(new_io)
old_io = Pry.input
Pry.input = new_io

View File

@ -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

223
test/test_command.rb Normal file
View File

@ -0,0 +1,223 @@
require 'helper'
describe "Pry::Command" do
before do
@set = Pry::CommandSet.new
end
describe 'call_safely' do
it 'should display a message if gems are missing' do
cmd = @set.command_class "ford-prefect", "From a planet near Beetlegeuse", :requires_gem => %w(ghijkl) do
#
end
mock_command(cmd, %w(hello world)).output.should =~ /install-command ford-prefect/
end
it 'should abort early if arguments are required' do
cmd = @set.command_class 'arthur-dent', "Doesn't understand Thursdays", :argument_required => true do
#
end
lambda {
mock_command(cmd, %w())
}.should.raise(Pry::CommandError)
end
it 'should return VOID without keep_retval' do
cmd = @set.command_class 'zaphod-beeblebrox', "Likes pan-Galactic Gargle Blasters" do
def process
3
end
end
mock_command(cmd).return.should == Pry::Command::VOID_VALUE
end
it 'should return the return value with keep_retval' do
cmd = @set.command_class 'tricia-mcmillian', "a.k.a Trillian", :keep_retval => true do
def process
5
end
end
mock_command(cmd).return.should == 5
end
it 'should call hooks in the right order' do
cmd = @set.command_class 'marvin', "Pained by the diodes in his left side" do
def process
output.puts 3 + args[0].to_i
end
end
@set.before_command 'marvin' do |i|
output.puts 2 + i.to_i
end
@set.before_command 'marvin' do |i|
output.puts 1 + i.to_i
end
@set.after_command 'marvin' do |i|
output.puts 4 + i.to_i
end
@set.after_command 'marvin' do |i|
output.puts 5 + i.to_i
end
mock_command(cmd, %w(2)).output.should == "3\n4\n5\n6\n7\n"
end
# TODO: This strikes me as rather silly...
it 'should return the value from the last hook with keep_retval' do
cmd = @set.command_class 'slartibartfast', "Designs Fjords", :keep_retval => true do
def process
22
end
end
@set.after_command 'slartibartfast' do
10
end
mock_command(cmd).return.should == 10
end
end
describe 'help' do
it 'should default to the description for blocky commands' do
@set.command 'oolon-colluphid', "Raving Atheist" do
#
end
mock_command(@set.commands['help'], %w(oolon-colluphid), :command_set => @set).output.should =~ /Raving Atheist/
end
it 'should use slop to generate the help for classy commands' do
@set.command_class 'eddie', "The ship-board computer" do
def options(opt)
opt.banner "Over-cheerful, and makes a ticking noise."
end
end
mock_command(@set.commands['help'], %w(eddie), :command_set => @set).output.should =~ /Over-cheerful/
end
it 'should provide --help for classy commands' do
cmd = @set.command_class 'agrajag', "Killed many times by Arthur" do
def options(opt)
opt.on :r, :retaliate, "Try to get Arthur back"
end
end
mock_command(cmd, %w(--help)).output.should =~ /--retaliate/
end
it 'should provide a -h for classy commands' do
cmd = @set.command_class 'zarniwoop', "On an intergalactic cruise, in his office." do
def options(opt)
opt.on :e, :escape, "Help zaphod escape the Total Perspective Vortex"
end
end
mock_command(cmd, %w(--help)).output.should =~ /Total Perspective Vortex/
end
end
describe 'context' do
context = {
:target => binding,
:output => StringIO.new,
:captures => [],
:eval_string => "eval-string",
:arg_string => "arg-string",
:command_set => @set,
:pry_instance => Object.new,
:command_processor => Object.new
}
it 'should capture lots of stuff from the hash passed to new before setup' do
cmd = @set.command_class 'fenchurch', "Floats slightly off the ground" do
define_method(:setup) do
self.context.should == context
target.should == context[:target]
target_self.should == context[:target].eval('self')
output.should == context[:output]
end
define_method(:process) do
captures.should.equal?(context[:captures])
eval_string.should == "eval-string"
arg_string.should == "arg-string"
command_set.should == @set
_pry_.should == context[:pry_instance]
command_processor.should == context[:command_processor]
end
end
cmd.new(context).call
end
end
describe 'classy api' do
it 'should call setup, then options, then process' do
cmd = @set.command_class 'rooster', "Has a tasty towel" do
def setup
output.puts "setup"
end
def options(opt)
output.puts "options"
end
def process
output.puts "process"
end
end
mock_command(cmd).output.should == "setup\noptions\nprocess\n"
end
it 'should raise a command error if process is not overridden' do
cmd = @set.command_class 'jeltz', "Commander of a Vogon constructor fleet" do
def proccces
#
end
end
lambda {
mock_command(cmd)
}.should.raise(Pry::CommandError)
end
it 'should work if neither options, nor setup is overridden' do
cmd = @set.command_class 'wowbagger', "Immortal, insulting.", :keep_retval => true do
def process
5
end
end
mock_command(cmd).return.should == 5
end
it 'should provide opts and args as provided by slop' do
cmd = @set.command_class 'lintilla', "One of 800,000,000 clones" do
def options(opt)
opt.on :f, :four, "A numeric four", :as => Integer, :optional => true
end
def process
args.should == ['four']
opts[:f].should == 4
end
end
mock_command(cmd, %w(--four 4 four))
end
end
end

View File

@ -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'
@ -159,9 +162,9 @@ describe Pry::CommandSet do
@set.desc('foo').should == 'bar'
end
it 'should return Pry::CommandContext::VOID_VALUE for commands by default' do
it 'should return Pry::Command::VOID_VALUE for commands by default' do
@set.command('foo') { 3 }
@set.run_command(@ctx, 'foo').should == Pry::CommandContext::VOID_VALUE
@set.run_command(@ctx, 'foo').should == Pry::Command::VOID_VALUE
end
it 'should be able to keep return values' do
@ -184,7 +187,7 @@ describe Pry::CommandSet do
end
@set.run_command(@ctx, 'foo')
Pry::CommandContext.new.should.not.respond_to :my_helper
Pry::Command.subclass('foo', '', {}, Module.new).new({:target => binding}).should.not.respond_to :my_helper
end
it 'should not recreate a new helper module when helpers is called' do
@ -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

View File

@ -7,8 +7,7 @@ describe "Pry::Commands" do
redirect_pry_io(InputTester.new("help ls", "exit-all"), str_output) do
pry
end
str_output.string.each_line.count.should == 1
str_output.string.should =~ /ls --help/
str_output.string.should =~ /Usage: ls/
end
it 'should display help for a regex command with a "listing"' do

View File

@ -43,7 +43,7 @@ describe Pry do
# bug fix for https://github.com/banister/pry/issues/93
it 'should not leak pry constants into Object namespace' do
input_string = "CommandContext"
input_string = "Command"
str_output = StringIO.new
o = Object.new
pry_tester = Pry.new(:input => StringIO.new(input_string),
@ -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