mirror of
https://github.com/pry/pry.git
synced 2022-11-09 12:35:05 -05:00
Merge branch 'classify-commands'
This commit is contained in:
commit
80d50c9bb4
16 changed files with 844 additions and 663 deletions
|
@ -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
296
lib/pry/command.rb
Normal 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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
223
test/test_command.rb
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue