Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
+148
View File
@@ -0,0 +1,148 @@
# frozen_string_literal: true
# (C) John Mair (banisterfiend) 2016
# MIT License
require 'pry/version'
require 'pry/last_exception'
require 'pry/forwardable'
require 'pry/helpers/base_helpers'
require 'pry/helpers/documentation_helpers'
require 'pry/helpers'
require 'pry/basic_object'
require 'pry/prompt'
require 'pry/code_object'
require 'pry/exceptions'
require 'pry/hooks'
require 'pry/input_completer'
require 'pry/command'
require 'pry/class_command'
require 'pry/block_command'
require 'pry/command_set'
require 'pry/syntax_highlighter'
require 'pry/editor'
require 'pry/history'
require 'pry/color_printer'
require 'pry/exception_handler'
require 'pry/system_command_handler'
require 'pry/control_d_handler'
require 'pry/command_state'
require 'pry/warning'
require 'pry/env'
Pry::Commands = Pry::CommandSet.new unless defined?(Pry::Commands)
require 'pry/commands/ls/config'
require 'pry/commands/ls/jruby_hacks'
require 'pry/commands/ls/methods_helper'
require 'pry/commands/ls/interrogatable'
require 'pry/commands/ls/grep'
require 'pry/commands/ls/formatter'
require 'pry/commands/ls/globals'
require 'pry/commands/ls/constants'
require 'pry/commands/ls/methods'
require 'pry/commands/ls/self_methods'
require 'pry/commands/ls/instance_vars'
require 'pry/commands/ls/local_names'
require 'pry/commands/ls/local_vars'
require 'pry/commands/ls/interrogatable'
require 'pry/commands/ls/ls_entity'
require 'pry/commands/ls/methods_helper'
require 'pry/commands/ls'
require 'pry/config/attributable'
require 'pry/config/value'
require 'pry/config/memoized_value'
require 'pry/config/lazy_value'
require 'pry/config'
require 'pry/input/simple_stdio'
require 'pry/pry_class'
require 'pry/pry_instance'
require 'pry/inspector'
require 'pry/pager'
require 'pry/indent'
require 'pry/object_path'
require 'pry/output'
require 'pry/input_lock'
require 'pry/repl'
require 'pry/code'
require 'pry/ring'
require 'pry/method'
require 'pry/wrapped_module'
require 'pry/wrapped_module/candidate'
require 'pry/slop'
require 'pry/cli'
require 'pry/core_extensions'
require 'pry/repl_file_loader'
require 'pry/code/loc'
require 'pry/code/code_range'
require 'pry/code/code_file'
require 'pry/method/weird_method_locator'
require 'pry/method/disowned'
require 'pry/method/patcher'
require 'pry/commands/amend_line'
require 'pry/commands/bang'
require 'pry/commands/bang_pry'
require 'pry/commands/cat'
require 'pry/commands/cat/abstract_formatter.rb'
require 'pry/commands/cat/input_expression_formatter.rb'
require 'pry/commands/cat/exception_formatter.rb'
require 'pry/commands/cat/file_formatter.rb'
require 'pry/commands/cd'
require 'pry/commands/change_inspector'
require 'pry/commands/change_prompt'
require 'pry/commands/clear_screen'
require 'pry/commands/code_collector'
require 'pry/commands/disable_pry'
require 'pry/commands/easter_eggs'
require 'pry/commands/edit'
require 'pry/commands/edit/exception_patcher'
require 'pry/commands/edit/file_and_line_locator'
require 'pry/commands/exit'
require 'pry/commands/exit_all'
require 'pry/commands/exit_program'
require 'pry/commands/find_method'
require 'pry/commands/fix_indent'
require 'pry/commands/help'
require 'pry/commands/hist'
require 'pry/commands/import_set'
require 'pry/commands/jump_to'
require 'pry/commands/list_inspectors'
require 'pry/commands/nesting'
require 'pry/commands/play'
require 'pry/commands/pry_backtrace'
require 'pry/commands/pry_version'
require 'pry/commands/raise_up'
require 'pry/commands/reload_code'
require 'pry/commands/reset'
require 'pry/commands/ri'
require 'pry/commands/save_file'
require 'pry/commands/shell_command'
require 'pry/commands/shell_mode'
require 'pry/commands/show_info'
require 'pry/commands/show_doc'
require 'pry/commands/show_input'
require 'pry/commands/show_source'
require 'pry/commands/stat'
require 'pry/commands/switch_to'
require 'pry/commands/toggle_color'
require 'pry/commands/watch_expression'
require 'pry/commands/watch_expression/expression.rb'
require 'pry/commands/whereami'
require 'pry/commands/wtf'
@@ -0,0 +1,10 @@
# frozen_string_literal: true
class Pry
class BasicObject < BasicObject
%i[Kernel File Dir LoadError ENV Pry].each do |constant|
const_set constant, ::Object.const_get(constant)
end
include Kernel
end
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
class Pry
# 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
# Call the block that was registered with this command.
# @param [Array<String>] args The arguments passed
# @return [Object] The return value of the block
def call(*args)
instance_exec(*normalize_method_args(block, args), &block)
end
def help
"#{command_options[:listing].to_s.ljust(18)} #{description}"
end
end
end
@@ -0,0 +1,194 @@
# frozen_string_literal: true
class Pry
# A super-class of 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#create_command}, and override the
# `options(opt)` method to set up an instance of Pry::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
class << self
# Ensure that subclasses inherit the options, description and
# match from a ClassCommand super class.
def inherited(klass)
klass.match match
klass.description description
klass.command_options options
end
def source
source_object.source
end
def doc
new.help
end
def source_location
source_object.source_location
end
def source_file
source_object.source_file
end
alias file source_file
def source_line
source_object.source_line
end
alias line source_line
private
# The object used to extract the source for the command.
#
# This should be a `Pry::Method(block)` for a command made with `create_command`
# and a `Pry::WrappedModule(self)` for a command that's a standard class.
# @return [Pry::WrappedModule, Pry::Method]
def source_object
@source_object ||= if name =~ /^[A-Z]/
Pry::WrappedModule(self)
else
Pry::Method(block)
end
end
end
attr_accessor :opts
attr_accessor :args
# Set up `opts` and `args`, and then call `process`.
#
# This method will display help if necessary.
#
# @param [Array<String>] args The arguments passed
# @return [Object] The return value of `process` or VOID_VALUE
def call(*args)
setup
self.opts = slop
self.args = opts.parse!(args)
if opts.present?(:help)
output.puts slop.help
void
else
process(*normalize_method_args(method(:process), args))
end
end
# Return the help generated by Pry::Slop for this command.
def help
slop.help
end
# Return an instance of Pry::Slop that can parse either subcommands or the
# options that this command accepts.
def slop
Pry::Slop.new do |opt|
opt.banner(unindent(self.class.banner))
subcommands(opt)
options(opt)
opt.on :h, :help, 'Show this message.'
end
end
# Generate shell completions
# @param [String] search The line typed so far
# @return [Array<String>] the words to complete
def complete(search)
slop.flat_map do |opt|
[opt.long && "--#{opt.long} " || opt.short && "-#{opt.short}"]
end.compact + super
end
# A method called just before `options(opt)` as part of `call`.
#
# This method 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 method to setup Pry::Slop commands so it can parse the subcommands your
# command expects. If you need to set up default values, use `setup`
# instead.
#
# @example A minimal example
# def subcommands(cmd)
# cmd.command :download do |opt|
# description 'Downloads a content from a server'
#
# opt.on :verbose, 'Use verbose output'
#
# run do |options, arguments|
# ContentDownloader.download(options, arguments)
# end
# end
# end
#
# @example Define the invocation block anywhere you want
# def subcommands(cmd)
# cmd.command :download do |opt|
# description 'Downloads a content from a server'
#
# opt.on :verbose, 'Use verbose output'
# end
# end
#
# def process
# # Perform calculations...
# opts.fetch_command(:download).run do |options, arguments|
# ContentDownloader.download(options, arguments)
# end
# # More calculations...
# end
def subcommands(cmd); end
# A method to setup Pry::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` method can be called to get the options that Pry::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 '#{command_name}' not implemented"
end
end
end
+211
View File
@@ -0,0 +1,211 @@
# frozen_string_literal: true
require 'stringio'
class Pry
# Manage the processing of command line options
class CLI
NoOptionsError = Class.new(StandardError)
class << self
# @return [Proc] The Proc defining the valid command line options.
attr_accessor :options
# @return [Array] The Procs that process the parsed options. Plugins can
# utilize this facility in order to add and process their own Pry
# options.
attr_accessor :option_processors
# @return [Array<String>] The input array of strings to process
# as CLI options.
attr_accessor :input_args
# Add another set of CLI options (a Pry::Slop block)
def add_options(&block)
if options
old_options = options
self.options = proc do
instance_exec(&old_options)
instance_exec(&block)
end
else
self.options = block
end
self
end
# Add a block responsible for processing parsed options.
def add_option_processor(&block)
self.option_processors ||= []
option_processors << block
self
end
# Clear `options` and `option_processors`
def reset
self.options = nil
self.option_processors = nil
end
def parse_options(args = ARGV)
unless options
raise NoOptionsError,
"No command line options defined! Use Pry::CLI.add_options to " \
"add command line options."
end
@pass_argv = args.index { |cli_arg| %w[- --].include?(cli_arg) }
if @pass_argv
slop_args = args[0...@pass_argv]
self.input_args = args.replace(args[@pass_argv + 1..-1])
else
self.input_args = slop_args = args
end
begin
opts = Pry::Slop.parse!(
slop_args,
help: true,
multiple_switches: false,
strict: true,
&options
)
rescue Pry::Slop::InvalidOptionError
# Display help message on unknown switches and exit.
puts Pry::Slop.new(&options)
Kernel.exit
end
Pry.initial_session_setup
Pry.final_session_setup
# Option processors are optional.
option_processors.each { |processor| processor.call(opts) } if option_processors
opts
end
def start(opts)
Kernel.exit if opts.help?
# invoked via cli
Pry.cli = true
# create the actual context
if opts[:context]
Pry.initial_session_setup
context = Pry.binding_for(eval(opts[:context])) # rubocop:disable Security/Eval
Pry.final_session_setup
else
context = Pry.toplevel_binding
end
if !@pass_argv && Pry::CLI.input_args.any? && Pry::CLI.input_args != ["pry"]
full_name = File.expand_path(Pry::CLI.input_args.first)
Pry.load_file_through_repl(full_name)
Kernel.exit
end
# Start the session (running any code passed with -e, if there is any)
Pry.start(context, input: StringIO.new(Pry.config.exec_string))
end
end
reset
end
end
# The default Pry command line options (before plugin options are included)
Pry::CLI.add_options do
banner(
"Usage: pry [OPTIONS]\n" \
"Start a Pry session.\n" \
"See http://pry.github.io/ for more information.\n" \
"Copyright (c) 2016 John Mair (banisterfiend)" \
)
on(
:e, :exec=, "A line of code to execute in context before the session starts"
) do |input|
Pry.config.exec_string += "\n" unless Pry.config.exec_string.empty?
Pry.config.exec_string += input
end
on "no-pager", "Disable pager for long output" do
Pry.config.pager = false
end
on "no-history", "Disable history loading" do
Pry.config.history_load = false
end
on "no-color", "Disable syntax highlighting for session" do
Pry.config.color = false
end
on "no-multiline", "Disables multiline (defaults to true with Reline)" do
Pry.config.multiline = false
end
on :f, "Suppress loading of pryrc" do
Pry.config.should_load_rc = false
Pry.config.should_load_local_rc = false
end
on :s, "select-plugin=", "Only load specified plugin (and no others)." do |_plugin_name|
warn "The --select-plugin option is deprecated and has no effect"
end
on :d, "disable-plugin=", "Disable a specific plugin." do |_plugin_name|
warn "The --disable-plugin option is deprecated and has no effect"
end
on "no-plugins", "Suppress loading of plugins." do
warn "The --no-plugins option is deprecated and has no effect"
end
on "plugins", "List installed plugins." do
warn "The --plugins option is deprecated and has no effect"
warn "Try using `gem list pry-`"
Kernel.exit
end
on "simple-prompt", "Enable simple prompt mode" do
Pry.config.prompt = Pry::Prompt[:simple]
end
on "noprompt", "No prompt mode" do
Pry.config.prompt = Pry::Prompt[:none]
end
on :r, :require=, "`require` a Ruby script at startup" do |file|
Pry.config.requires << file
end
on(:I=, "Add a path to the $LOAD_PATH", as: Array, delimiter: ":") do |load_path|
load_path.map! do |path|
%r{\A\./} =~ path ? path : File.expand_path(path)
end
$LOAD_PATH.unshift(*load_path)
end
on "gem", "Shorthand for -I./lib -rgemname" do |_load_path|
$LOAD_PATH.unshift("./lib")
Dir["./lib/*.rb"].each do |file|
Pry.config.requires << file
end
end
on :v, :version, "Display the Pry version" do
puts "Pry version #{Pry::VERSION} on Ruby #{RUBY_VERSION}"
Kernel.exit
end
on :c, :context=,
"Start the session in the specified context. Equivalent to " \
"`context.pry` in a session.",
default: "Pry.toplevel_binding"
end
+357
View File
@@ -0,0 +1,357 @@
# frozen_string_literal: true
require 'method_source'
class Pry
class << self
# Convert the given object into an instance of `Pry::Code`, if it isn't
# already one.
#
# @param [Code, Method, UnboundMethod, Proc, Pry::Method, String, Array,
# IO] obj
def Code(obj)
case obj
when Code
obj
when ::Method, UnboundMethod, Proc, Pry::Method
Code.from_method(obj)
else
Code.new(obj)
end
end
end
# `Pry::Code` is a class that encapsulates lines of source code and their
# line numbers and formats them for terminal output. It can read from a file
# or method definition or be instantiated with a `String` or an `Array`.
#
# In general, the formatting methods in `Code` return a new `Code` object
# which will format the text as specified when `#to_s` is called. This allows
# arbitrary chaining of formatting methods without mutating the original
# object.
class Code
class << self
include MethodSource::CodeHelpers
# Instantiate a `Code` object containing code loaded from a file or
# Pry's line buffer.
#
# @param [String] filename The name of a file, or "(pry)".
# @param [Symbol] code_type The type of code the file contains.
# @return [Code]
def from_file(filename, code_type = nil)
code_file = CodeFile.new(filename, code_type)
new(code_file.code, 1, code_file.code_type)
end
# Instantiate a `Code` object containing code extracted from a
# `::Method`, `UnboundMethod`, `Proc`, or `Pry::Method` object.
#
# @param [::Method, UnboundMethod, Proc, Pry::Method] meth The method
# object.
# @param [Integer, nil] start_line The line number to start on, or nil to
# use the method's original line numbers.
# @return [Code]
def from_method(meth, start_line = nil)
meth = Pry::Method(meth)
start_line ||= meth.source_line || 1
new(meth.source, start_line, meth.source_type)
end
# Attempt to extract the source code for module (or class) `mod`.
#
# @param [Module, Class] mod The module (or class) of interest.
# @param [Integer] candidate_rank The module candidate (by rank)
# to use (see `Pry::WrappedModule::Candidate` for more information).
# @param [Integer, nil] start_line The line number to start on, or nil to
# use the method's original line numbers.
# @return [Code]
def from_module(mod, candidate_rank = 0, start_line = nil)
candidate = Pry::WrappedModule(mod).candidate(candidate_rank)
start_line ||= candidate.line
new(candidate.source, start_line, :ruby)
end
end
# @return [Symbol] The type of code stored in this wrapper.
attr_accessor :code_type
# Instantiate a `Code` object containing code from the given `Array`,
# `String`, or `IO`. The first line will be line 1 unless specified
# otherwise. If you need non-contiguous line numbers, you can create an
# empty `Code` object and then use `#push` to insert the lines.
#
# @param [Array<String>, String, IO] lines
# @param [Integer?] start_line
# @param [Symbol?] code_type
def initialize(lines = [], start_line = 1, code_type = :ruby)
lines = lines.lines if lines.is_a? String
@lines = lines.each_with_index.map do |line, lineno|
LOC.new(line, lineno + start_line.to_i)
end
@code_type = code_type
@with_marker = @with_indentation = @with_line_numbers = nil
end
# Append the given line. +lineno+ is one more than the last existing
# line, unless specified otherwise.
#
# @param [String] line
# @return [void]
def push(line)
line_number = @lines.any? ? @lines.last.lineno + 1 : 1
@lines.push(LOC.new(line, line_number))
end
alias << push
# Filter the lines using the given block.
#
# @yield [LOC]
# @return [Code]
def select(&block)
alter do
@lines = @lines.select(&block)
end
end
# Filter the lines using the given block.
#
# @yield [LOC]
# @return [Code]
def reject(&block)
alter do
@lines = @lines.reject(&block)
end
end
# Remove all lines that aren't in the given range, expressed either as a
# `Range` object or a first and last line number (inclusive). Negative
# indices count from the end of the array of lines.
#
# @param [Range, Integer] start_line
# @param [Integer?] end_line
# @return [Code]
def between(start_line, end_line = nil)
return self unless start_line
code_range = CodeRange.new(start_line, end_line)
alter do
@lines = @lines[code_range.indices_range(@lines)] || []
end
end
# Take `num_lines` from `start_line`, forward or backwards.
#
# @param [Integer] start_line
# @param [Integer] num_lines
# @return [Code]
def take_lines(start_line, num_lines)
start_idx =
if start_line >= 0
@lines.index { |loc| loc.lineno >= start_line } || @lines.length
else
[@lines.length + start_line, 0].max
end
alter do
@lines = @lines.slice(start_idx, num_lines)
end
end
# Remove all lines except for the +lines+ up to and excluding +lineno+.
#
# @param [Integer] lineno
# @param [Integer] lines
# @return [Code]
def before(lineno, lines = 1)
return self unless lineno
select do |loc|
loc.lineno >= lineno - lines && loc.lineno < lineno
end
end
# Remove all lines except for the +lines+ on either side of and including
# +lineno+.
#
# @param [Integer] lineno
# @param [Integer] lines
# @return [Code]
def around(lineno, lines = 1)
return self unless lineno
select do |loc|
loc.lineno >= lineno - lines && loc.lineno <= lineno + lines
end
end
# Remove all lines except for the +lines+ after and excluding +lineno+.
#
# @param [Integer] lineno
# @param [Integer] lines
# @return [Code]
def after(lineno, lines = 1)
return self unless lineno
select do |loc|
loc.lineno > lineno && loc.lineno <= lineno + lines
end
end
# Remove all lines that don't match the given `pattern`.
#
# @param [Regexp] pattern
# @return [Code]
def grep(pattern)
return self unless pattern
pattern = Regexp.new(pattern)
select do |loc|
loc.line =~ pattern
end
end
# Format output with line numbers next to it, unless `y_n` is falsy.
#
# @param [Boolean?] y_n
# @return [Code]
def with_line_numbers(y_n = true)
alter do
@with_line_numbers = y_n
end
end
# Format output with a marker next to the given +lineno+, unless +lineno+ is
# falsy.
#
# @param [Integer?] lineno
# @return [Code]
def with_marker(lineno = 1)
alter do
@with_marker = !!lineno
@marker_lineno = lineno
end
end
# Format output with the specified number of spaces in front of every line,
# unless `spaces` is falsy.
#
# @param [Integer?] spaces
# @return [Code]
def with_indentation(spaces = 0)
alter do
@with_indentation = !!spaces
@indentation_num = spaces
end
end
# @return [Integer] the number of digits in the last line.
def max_lineno_width
!@lines.empty? ? @lines.last.lineno.to_s.length : 0
end
# @return [String] a formatted representation (based on the configuration of
# the object).
def to_s
print_to_output(''.dup, false)
end
# @return [String] a (possibly highlighted) copy of the source code.
def highlighted
print_to_output(''.dup, true)
end
# Writes a formatted representation (based on the configuration of the
# object) to the given output, which must respond to `#<<`.
def print_to_output(output, color = false)
@lines.each do |loc|
loc = loc.dup
loc.colorize(@code_type) if color
loc.add_line_number(max_lineno_width, color) if @with_line_numbers
loc.add_marker(@marker_lineno) if @with_marker
loc.indent(@indentation_num) if @with_indentation
output << loc.line
output << "\n"
end
output
end
# Get the comment that describes the expression on the given line number.
#
# @param [Integer] line_number (1-based)
# @return [String] the code.
def comment_describing(line_number)
self.class.comment_describing(raw, line_number)
end
# Get the multiline expression that starts on the given line number.
#
# @param [Integer] line_number (1-based)
# @return [String] the code.
def expression_at(line_number, consume = 0)
self.class.expression_at(raw, line_number, consume: consume)
end
# Get the (approximate) Module.nesting at the give line number.
#
# @param [Integer] line_number line number starting from 1
# @return [Array<Module>] a list of open modules.
def nesting_at(line_number)
Pry::Indent.nesting_at(raw, line_number)
end
# Return an unformatted String of the code.
#
# @return [String]
def raw
@lines.map(&:line).join("\n") << "\n"
end
# Return the number of lines stored.
#
# @return [Integer]
def length
@lines ? @lines.length : 0
end
# Two `Code` objects are equal if they contain the same lines with the same
# numbers. Otherwise, call `to_s` and `chomp` and compare as Strings.
#
# @param [Code, Object] other
# @return [Boolean]
def ==(other)
if other.is_a?(Code)
other_lines = other.instance_variable_get(:@lines)
@lines.each_with_index.all? { |loc, i| loc == other_lines[i] }
else
to_s.chomp == other.to_s.chomp
end
end
# Forward any missing methods to the output of `#to_s`.
def method_missing(method_name, *args, &block)
if (string = to_s).respond_to?(method_name)
string.__send__(method_name, *args, &block)
else
super
end
end
undef =~ if method_defined?(:=~)
# Check whether String responds to missing methods.
def respond_to_missing?(method_name, include_private = false)
''.respond_to?(method_name, include_private) || super
end
protected
# An abstraction of the `dup.instance_eval` pattern used throughout this
# class.
def alter(&block)
dup.tap { |o| o.instance_eval(&block) }
end
end
end
@@ -0,0 +1,114 @@
# frozen_string_literal: true
require 'method_source'
class Pry
class CodeFile
DEFAULT_EXT = '.rb'.freeze
# List of all supported languages.
# @return [Hash]
EXTENSIONS = {
%w[.py] => :python,
%w[.js] => :javascript,
%w[.css] => :css,
%w[.xml] => :xml,
%w[.php] => :php,
%w[.html] => :html,
%w[.diff] => :diff,
%w[.java] => :java,
%w[.json] => :json,
%w[.c .h] => :c,
%w[.rhtml] => :rhtml,
%w[.yaml .yml] => :yaml,
%w[.cpp .hpp .cc .h .cxx] => :cpp,
%w[.rb .ru .irbrc .gemspec .pryrc .rake] => :ruby
}.freeze
FILES = {
%w[Gemfile Rakefile Guardfile Capfile] => :ruby
}.freeze
# Store the current working directory. This allows show-source etc. to work if
# your process has changed directory since boot. [Issue #675]
INITIAL_PWD = Dir.pwd
# @return [Symbol] The type of code stored in this wrapper.
attr_reader :code_type
# @param [String] filename The name of a file with code to be detected
# @param [Symbol] code_type The type of code the `filename` contains
def initialize(filename, code_type = type_from_filename(filename))
@filename = filename
@code_type = code_type
end
# @return [String] The code contained in the current `@filename`.
def code
if @filename == Pry.eval_path
Pry.line_buffer.drop(1)
elsif Pry::Method::Patcher.code_for(@filename)
Pry::Method::Patcher.code_for(@filename)
else
path = abs_path
@code_type = type_from_filename(path)
File.read(path)
end
end
private
# @raise [MethodSource::SourceNotFoundError] if the `filename` is not
# readable for some reason.
# @return [String] absolute path for the given `filename`.
def abs_path
code_path.detect { |path| readable?(path) } ||
raise(MethodSource::SourceNotFoundError,
"Cannot open #{@filename.inspect} for reading.")
end
# @param [String] path
# @return [Boolean] if the path, with or without the default ext,
# is a readable file then `true`, otherwise `false`.
def readable?(path)
File.readable?(path) && !File.directory?(path) ||
File.readable?(path << DEFAULT_EXT)
end
# @return [Array] All the paths that contain code that Pry can use for its
# API's. Skips directories.
def code_path
[from_pwd, from_pry_init_pwd, *from_load_path]
end
# @param [String] filename
# @param [Symbol] default (:unknown) the file type to assume if none could be
# detected.
# @return [Symbol, nil] The SyntaxHighlighter type of a file from its
# extension, or `nil` if `:unknown`.
def type_from_filename(filename, default = :unknown)
_, @code_type = EXTENSIONS.find do |k, _|
k.any? { |ext| ext == File.extname(filename) }
end || FILES.find do |k, _|
k.any? { |file_name| file_name == File.basename(filename) }
end
code_type || default
end
# @return [String]
def from_pwd
File.expand_path(@filename, Dir.pwd)
end
# @return [String]
def from_pry_init_pwd
File.expand_path(@filename, INITIAL_PWD)
end
# @return [String]
def from_load_path
$LOAD_PATH.map { |path| File.expand_path(@filename, path) }
end
end
end
@@ -0,0 +1,73 @@
# frozen_string_literal: true
class Pry
class Code
# Represents a range of lines in a code listing.
#
# @api private
class CodeRange
# @param [Integer] start_line
# @param [Integer?] end_line
def initialize(start_line, end_line = nil)
@start_line = start_line
@end_line = end_line
force_set_end_line
end
# @param [Array<LOC>] lines
# @return [Range]
def indices_range(lines)
Range.new(*indices(lines))
end
private
attr_reader :start_line
attr_reader :end_line
# If `end_line` is equal to `nil`, then calculate it from the first
# parameter, `start_line`. Otherwise, leave it as it is.
# @return [void]
def force_set_end_line
if start_line.is_a?(Range)
set_end_line_from_range
else
@end_line ||= start_line
end
end
# Finds indices of `start_line` and `end_line` in the given Array of
# +lines+.
#
# @param [Array<LOC>] lines
# @return [Array<Integer>]
def indices(lines)
[find_start_index(lines), find_end_index(lines)]
end
# @return [Integer]
def find_start_index(lines)
return start_line if start_line < 0
lines.index { |loc| loc.lineno >= start_line } || lines.length
end
# @return [Integer]
def find_end_index(lines)
return end_line if end_line < 0
(lines.index { |loc| loc.lineno > end_line } || 0) - 1
end
# For example, if the range is 4..10, then `start_line` would be equal to
# 4 and `end_line` to 10.
# @return [void]
def set_end_line_from_range
@end_line = start_line.last
@end_line -= 1 if start_line.exclude_end?
@start_line = start_line.first
end
end
end
end
+105
View File
@@ -0,0 +1,105 @@
# frozen_string_literal: true
class Pry
class Code
# Represents a line of code (which may, in fact, contain multiple lines if
# the entirety was eval'd as a single unit following the `edit` command).
#
# A line of code is a tuple, which consists of a line and a line number. A
# `LOC` object's state (namely, the line parameter) can be changed via
# instance methods. `Pry::Code` heavily uses this class.
#
# @api private
# @example
# loc = LOC.new("def example\n :example\nend", 1)
# puts loc.line
# def example
# :example
# end
# #=> nil
#
# loc.indent(3)
# loc.line #=> " def example\n :example\nend"
class LOC
# @return [Array<String, Integer>]
attr_reader :tuple
# @param [String] line The line of code.
# @param [Integer] lineno The position of the +line+.
def initialize(line, lineno)
@tuple = [line.chomp, lineno.to_i]
end
# @return [Boolean]
def ==(other)
other.tuple == tuple
end
def dup
self.class.new(line, lineno)
end
# @return [String]
def line
tuple.first
end
# @return [Integer]
def lineno
tuple.last
end
# Paints the `line` of code.
#
# @param [Symbol] code_type
# @return [void]
def colorize(code_type)
tuple[0] = SyntaxHighlighter.highlight(line, code_type)
end
# Prepends the line number `lineno` to the `line`.
#
# @param [Integer] max_width
# @return [void]
def add_line_number(max_width = 0, color = false)
padded = lineno.to_s.rjust(max_width)
colorized_lineno =
if color
Pry::Helpers::BaseHelpers.colorize_code(padded)
else
padded
end
properly_padded_line = handle_multiline_entries_from_edit_command(line, max_width)
tuple[0] = "#{colorized_lineno}: #{properly_padded_line}"
end
# Prepends a marker "=>" or an empty marker to the +line+.
#
# @param [Integer] marker_lineno If it is equal to the `lineno`, then
# prepend a hashrocket. Otherwise, an empty marker.
# @return [void]
def add_marker(marker_lineno)
tuple[0] =
if lineno == marker_lineno
" => #{line}"
else
" #{line}"
end
end
# Indents the `line` with +distance+ spaces.
#
# @param [Integer] distance
# @return [void]
def indent(distance)
tuple[0] = "#{' ' * distance}#{line}"
end
def handle_multiline_entries_from_edit_command(line, max_width)
line.split("\n").map.with_index do |inner_line, i|
i.zero? ? inner_line : "#{' ' * (max_width + 2)}#{inner_line}"
end.join("\n")
end
end
end
end
+197
View File
@@ -0,0 +1,197 @@
# frozen_string_literal: true
class Pry
# This class is responsible for taking a string (identifying a
# command/class/method/etc) and returning the relevant type of object.
# For example, if the user looks up "show-source" then a
# `Pry::Command` will be returned. Alternatively, if the user passes in "Pry#repl" then
# a `Pry::Method` object will be returned.
#
# The `CodeObject.lookup` method is responsible for 1. figuring out what kind of
# object the user wants (applying precedence rules in doing so -- i.e methods
# get precedence over commands with the same name) and 2. Returning
# the appropriate object. If the user fails to provide a string
# identifier for the object (i.e they pass in `nil` or "") then the
# object looked up will be the 'current method' or 'current class'
# associated with the Binding.
#
# TODO: This class is a clusterfuck. We need a much more robust
# concept of what a "Code Object" really is. Currently
# commands/classes/candidates/methods and so on just share a very
# ill-defined interface.
class CodeObject
module Helpers
# we need this helper as some Pry::Method objects can wrap Procs
# @return [Boolean]
def real_method_object?
is_a?(::Method) || is_a?(::UnboundMethod)
end
def c_method?
real_method_object? && source_type == :c
end
def module_with_yard_docs?
is_a?(WrappedModule) && yard_docs?
end
def command?
is_a?(Module) && self <= Pry::Command
end
# @return [Boolean] `true` if this module was defined by means of the C API,
# `false` if it's a Ruby module.
# @note If a module defined by C was extended with a lot of methods written
# in Ruby, this method would fail.
def c_module?
return unless is_a?(WrappedModule)
method_locations = wrapped.methods(false).map do |m|
wrapped.method(m).source_location
end
method_locations.concat(
wrapped.instance_methods(false).map do |m|
wrapped.instance_method(m).source_location
end
)
c_methods = method_locations.grep(nil).count
ruby_methods = method_locations.count - c_methods
c_methods > ruby_methods
end
end
include Pry::Helpers::CommandHelpers
class << self
def lookup(str, pry_instance, options = {})
co = new(str, pry_instance, options)
co.default_lookup || co.method_or_class_lookup ||
co.command_lookup || co.empty_lookup
end
end
attr_accessor :str
attr_accessor :target
attr_accessor :pry_instance
attr_accessor :super_level
def initialize(str, pry_instance, options = {})
options = {
super: 0
}.merge!(options)
@str = str
@pry_instance = pry_instance
@target = pry_instance.current_context
@super_level = options[:super]
end
# TODO: just make it so find_command_by_match_or_listing doesn't raise?
def command_lookup
pry_instance.commands.find_command_by_match_or_listing(str)
rescue StandardError
nil
end
# when no parameter is given (i.e CodeObject.lookup(nil)), then we
# lookup the 'current object' from the binding.
def empty_lookup
return nil if str && !str.empty?
obj = if internal_binding?(target)
mod = target_self.is_a?(Module) ? target_self : target_self.class
Pry::WrappedModule(mod)
else
Pry::Method.from_binding(target)
end
# respect the super level (i.e user might have specified a
# --super flag to show-source)
lookup_super(obj, super_level)
end
# lookup variables and constants and `self` that are not modules
def default_lookup
# we skip instance methods as we want those to fall through to
# method_or_class_lookup()
if safe_to_evaluate?(str) && !looks_like_an_instance_method?(str)
obj = target.eval(str)
# restrict to only objects we KNOW for sure support the full API
# Do NOT support just any object that responds to source_location
if sourcable_object?(obj)
Pry::Method(obj)
elsif !obj.is_a?(Module)
Pry::WrappedModule(obj.class)
end
end
rescue Pry::RescuableException
nil
end
def method_or_class_lookup
obj =
case str
when /\S+\(\)\z/
Pry::Method.from_str(str.sub(/\(\)\z/, ''), target) ||
Pry::WrappedModule.from_str(str, target)
else
Pry::WrappedModule.from_str(str, target) ||
Pry::Method.from_str(str, target)
end
lookup_super(obj, super_level)
end
private
def sourcable_object?(obj)
[::Proc, ::Method, ::UnboundMethod].any? { |o| obj.is_a?(o) }
end
# Returns true if `str` looks like a method, i.e Klass#method
# We need to consider this case because method lookups should fall
# through to the `method_or_class_lookup()` method but a
# defined?() on a "Klass#method` string will see the `#` as a
# comment and only evaluate the `Klass` part.
# @param [String] str
# @return [Boolean] Whether the string looks like an instance method.
def looks_like_an_instance_method?(str)
str =~ /\S#\S/
end
# We use this method to decide whether code is safe to eval. Method's are
# generally not, but everything else is.
# TODO: is just checking != "method" enough??
# TODO: see duplication of this method in Pry::WrappedModule
# @param [String] str The string to lookup
# @return [Boolean]
def safe_to_evaluate?(str)
return true if str.strip == "self"
return false if str =~ /%/
kind = target.eval("defined?(#{str})")
kind =~ /variable|constant/
end
def target_self
target.eval('self')
end
# grab the nth (`super_level`) super of `obj
# @param [Object] obj
# @param [Fixnum] super_level How far up the super chain to ascend.
def lookup_super(obj, super_level)
return unless obj
sup = obj.super(super_level)
raise Pry::CommandError, "No superclass found for #{obj.wrapped}" unless sup
sup
end
end
end
@@ -0,0 +1,66 @@
# frozen_string_literal: true
require 'pp'
require 'English'
class Pry
# PP subclass for streaming inspect output in color.
class ColorPrinter < ::PP
Pry::SyntaxHighlighter.overwrite_coderay_comment_token!
def self.default(_output, value, pry_instance)
pry_instance.pager.open do |pager|
pager.print pry_instance.config.output_prefix
pp(value, pager, pry_instance.output.width - 1)
end
end
def self.pp(obj, output = $DEFAULT_OUTPUT, max_width = 79)
queue = ColorPrinter.new(output, max_width, "\n")
queue.guard_inspect_key { queue.pp(obj) }
queue.flush
output << "\n"
end
def pp(object)
return super unless object.is_a?(String)
# Avoid calling Ruby 2.4+ String#pretty_print that prints multiline
# Strings prettier
text(object.inspect)
rescue StandardError => exception
raise if exception.is_a?(Pry::Pager::StopPaging)
text(highlight_object_literal(inspect_object(object)))
end
def text(str, max_width = str.length)
if str.include?("\e[")
super("#{str}\e[0m", max_width)
elsif str.start_with?('#<') || %w[= >].include?(str)
super(highlight_object_literal(str), max_width)
else
super(SyntaxHighlighter.highlight(str), max_width)
end
end
private
def highlight_object_literal(object_literal)
code = Pry::SyntaxHighlighter.keyword_token_color
obj_color = code.start_with?("\e") ? code : "\e[0m\e[0;#{code}m"
"#{obj_color}#{object_literal}\e[0m"
end
def inspect_object(object)
object.inspect
rescue StandardError
# Read the class name off of the singleton class to provide a default
# inspect.
singleton = class << object; self; end
ancestors = Pry::Method.safe_send(singleton, :ancestors)
klass = ancestors.find { |k| k != singleton }
"#<#{klass}:0x#{object.__id__.to_s(16)}>"
end
end
end
+520
View File
@@ -0,0 +1,520 @@
# frozen_string_literal: true
require 'delegate'
require 'shellwords'
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#create_command} which creates a ClassCommand. Please don't
# use this class directly.
class Command
extend Helpers::DocumentationHelpers
extend CodeObject::Helpers
include Pry::Helpers::BaseHelpers
include Pry::Helpers::CommandHelpers
include Pry::Helpers::Text
# 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#create_command}).
class << self
attr_writer :block
attr_writer :description
attr_writer :command_options
attr_writer :match
def match(arg = nil)
if arg
@command_options ||= default_options(arg)
@command_options[:listing] = arg.is_a?(String) ? arg : arg.inspect
@match = arg
end
@match ||= nil
end
# Define or get the command's description
def description(arg = nil)
@description = arg if arg
@description ||= nil
end
# Define or get the command's options
def command_options(arg = nil)
@command_options ||= default_options(match)
@command_options.merge!(arg) if arg
@command_options
end
# backward compatibility
alias options command_options
alias options= command_options=
# Define or get the command's banner
def banner(arg = nil)
@banner = arg if arg
@banner ||= description
end
def block
@block || instance_method(:process)
end
def source
file, line = block.source_location
strip_leading_whitespace(Pry::Code.from_file(file).expression_at(line))
end
def doc
new.help
end
def source_file
Array(block.source_location).first
end
alias file source_file
def source_line
Array(block.source_location).last
end
alias line source_line
def default_options(match)
{
keep_retval: false,
argument_required: false,
interpolate: true,
shellwords: true,
listing: (match.is_a?(String) ? match : match.inspect),
use_prefix: true,
takes_block: false
}
end
def name
super.to_s == "" ? "#<class(Pry::Command #{match.inspect})>" : super
end
def inspect
name
end
def command_name
options[:listing]
end
# Create a new command with the given properties.
# @param [String, Regex] match The thing that triggers this command
# @param [String] description The description to appear in `help`
# @param [Hash] options Behavioral options (see {Pry::CommandSet#command})
# @param [Module] helpers A module of helper functions to be included.
# @yield optional, used for BlockCommands
# @return [Class] (a subclass of {Pry::Command})
def subclass(match, description, options, helpers, &block)
klass = Class.new(self)
klass.send(:include, helpers)
klass.match = match
klass.description = description
klass.command_options = options
klass.block = block
klass
end
# Should this command be called for the given line?
# @param [String] val A line input at the REPL
# @return [Boolean]
def matches?(val)
command_regex =~ val
end
# How well does this command match the given line?
#
# Higher scores are better because they imply that this command matches
# the line more closely.
#
# The score is calculated by taking the number of characters at the start
# of the string that are used only to identify the command, not as part of
# the arguments.
#
# @example
# /\.(.*)/.match_score(".foo") #=> 1
# /\.*(.*)/.match_score("...foo") #=> 3
# 'hi'.match_score("hi there") #=> 2
#
# @param [String] val A line input at the REPL
# @return [Fixnum]
def match_score(val)
if command_regex =~ val
if Regexp.last_match.size > 1
Regexp.last_match.begin(1)
else
Regexp.last_match.end(0)
end
else
-1
end
end
def command_regex
prefix = convert_to_regex(Pry.config.command_prefix)
prefix = "(?:#{prefix})?" unless options[:use_prefix]
/\A#{prefix}#{convert_to_regex(match)}(?!\S)/
end
def convert_to_regex(obj)
case obj
when String
Regexp.escape(obj)
else
obj
end
end
# The group in which the command should be displayed in "help" output.
# This is usually auto-generated from directory naming, but it can be
# manually overridden if necessary.
# Group should not be changed once it is initialized.
def group(name = nil)
@group ||= begin
name || case Pry::Method(block).source_file
when %r{/pry/.*_commands/(.*).rb}
Regexp.last_match(1).capitalize.tr('_', " ")
when /(pry-\w+)-([\d\.]+([\w\.]+)?)/
name = Regexp.last_match(1)
version = Regexp.last_match(2)
"#{name} (v#{version})"
when /pryrc/
"pryrc"
else
"(other)"
end
end
end
def state
Pry::CommandState.default.state_for(self)
end
end
# Properties of one execution of a command (passed by {Pry#run_command} 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 :hooks
attr_accessor :pry_instance
alias _pry_= pry_instance=
# The block we pass *into* a command so long as `:takes_block` is
# not equal to `false`
# @example
# my-command | do
# puts "block content"
# end
attr_accessor :command_block
# 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.eval_string = context[:eval_string]
self.command_set = context[:command_set]
self.hooks = context[:hooks]
self.pry_instance = context[:pry_instance]
end
# Make those properties accessible to instances
def name
self.class.name
end
def match
self.class.match
end
def description
self.class.description
end
def block
self.class.block
end
def command_options
self.class.options
end
def command_name
self.class.command_name
end
def source
self.class.source
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)
command_string = pry_instance.config.command_prefix.to_s + command_string
complete_string = "#{command_string} #{args.join(' ')}".rstrip
command_set.process_line(complete_string, context)
end
def commands
command_set.to_hash
end
def void
VOID_VALUE
end
def _pry_
Pry::Warning.warn('_pry_ is deprecated, use pry_instance instead')
pry_instance
end
# @return [Object] The value of `self` inside the `target` binding.
def target_self
target.eval('self')
end
# @return [Hash] Pry commands can store arbitrary state
# here. This state persists between subsequent command invocations.
# All state saved here is unique to the command, it does not
# need to be namespaced.
# @example
# state.my_state = "my state" # this will not conflict with any
# # `state.my_state` used in another command.
def state
self.class.state
end
# Revaluate the string (str) and perform interpolation.
# @param [String] str The string to reevaluate with interpolation.
#
# @return [String] The reevaluated string with interpolations
# applied (if any).
def interpolate_string(str)
dumped_str = str.dump
if dumped_str.gsub!(/\\\#\{/, '#{')
target.eval(dumped_str)
else
str
end
end
# Display a warning if a command collides with a local/method in
# the current scope.
def check_for_command_collision(command_match, arg_string)
collision_type = target.eval("defined?(#{command_match})")
collision_type ||= 'local-variable' if arg_string =~ %r{\A\s*[-+*/%&|^]*=}
if collision_type
output.puts(
"#{Helpers::Text.bold('WARNING:')} Calling Pry command '#{command_match}', " \
"which conflicts with a #{collision_type}.\n\n"
)
end
rescue Pry::RescuableException # rubocop:disable Lint/HandleExceptions
end
# Extract necessary information from a line that Command.matches? this
# command.
#
# Returns an array of four elements:
#
# ```
# [String] the portion of the line that matched with the Command match
# [String] a string of all the arguments (i.e. everything but the match)
# [Array] the captures caught by the command_regex
# [Array] the arguments obtained by splitting the arg_string
# ```
#
# @param [String] val The line of input
# @return [Array]
def tokenize(val)
val = interpolate_string(val) if command_options[:interpolate]
self.class.command_regex =~ val
# please call Command.matches? before Command#call_safely
unless Regexp.last_match
raise CommandError, "fatal: called a command which didn't match?!"
end
captures = Regexp.last_match.captures
pos = Regexp.last_match.end(0)
arg_string = val[pos..-1]
# remove the one leading space if it exists
arg_string.slice!(0) if arg_string.start_with?(" ")
# process and pass a block if one is found
pass_block(arg_string) if command_options[:takes_block]
args =
if arg_string
if command_options[:shellwords]
Shellwords.shellwords(arg_string)
else
arg_string.split(" ")
end
else
[]
end
[val[0..pos].rstrip, arg_string, captures, args]
end
# Process a line that Command.matches? this command.
# @param [String] line The line to process
# @return [Object, Command::VOID_VALUE]
def process_line(line)
command_match, arg_string, captures, args = tokenize(line)
if Pry.config.collision_warning
check_for_command_collision(command_match, arg_string)
end
self.arg_string = arg_string
self.captures = captures
call_safely(*(captures + args))
end
# Generate completions for this command
#
# @param [String] _search The line typed so far
# @return [Array<String>] Completion words
def complete(_search)
[]
end
private
# Run the command with the given `args`.
#
# This is a public wrapper around `#call` which ensures all preconditions
# are met.
#
# @param [Array<String>] args The arguments to pass to this command.
# @return [Object] The return value of the `#call` method, or
# {Command::VOID_VALUE}.
def call_safely(*args)
if command_options[:argument_required] && args.empty?
raise CommandError, "The command '#{command_name}' requires an argument."
end
ret = use_unpatched_symbol do
call_with_hooks(*args)
end
command_options[:keep_retval] ? ret : void
end
def use_unpatched_symbol
call_method = Symbol.method_defined?(:call) && Symbol.instance_method(:call)
Symbol.class_eval { undef :call } if call_method
yield
ensure
Symbol.instance_eval { define_method(:call, call_method) } if call_method
end
# Pass a block argument to a command.
# @param [String] arg_string The arguments (as a string) passed to the command.
# We inspect these for a '| do' or a '| {' and if we find it we use it
# to start a block input sequence. Once we have a complete
# block, we save it to an accessor that can be retrieved from the command context.
# Note that if we find the '| do' or '| {' we delete this and the
# elements following it from `arg_string`.
def pass_block(arg_string)
# Workaround for weird JRuby bug where rindex in this case can return nil
# even when there's a match.
arg_string.scan(/\| *(?:do|\{)/)
block_index = $LAST_MATCH_INFO && $LAST_MATCH_INFO.offset(0)[0]
return unless block_index
block_init_string = arg_string.slice!(block_index..-1)[1..-1]
prime_string = "proc #{block_init_string}\n"
block_string =
if !Pry::Code.complete_expression?(prime_string)
pry_instance.r(target, prime_string)
else
prime_string
end
begin
self.command_block = target.eval(block_string)
rescue Pry::RescuableException
raise CommandError, "Incomplete block definition."
end
end
def find_hooks(event)
event_name = "#{event}_#{command_name}"
(hooks || Pry.hooks || self.class.hooks).get_hooks(event_name).values
end
def before_hooks
find_hooks('before')
end
def after_hooks
find_hooks('after')
end
# Run the `#call` method and all the registered hooks.
# @param [Array<String>] args The arguments to `#call`
# @return [Object] The return value from `#call`
def call_with_hooks(*args)
before_hooks.each { |block| instance_exec(*args, &block) }
ret = call(*args)
after_hooks.each do |block|
ret = instance_exec(*args, &block)
end
ret
end
# Normalize method arguments according to its arity.
#
# @param [Integer] method
# @param [Array] args
# @return [Array] a (possibly shorter) array of the arguments to pass
def normalize_method_args(method, args)
case method.arity
when -1
args
when 0
[]
else
args.values_at(*(0..(method.arity - 1)).to_a)
end
end
end
end
+418
View File
@@ -0,0 +1,418 @@
# frozen_string_literal: true
class Pry
class NoCommandError < StandardError
def initialize(match, owner)
super "Command '#{match}' not found in command set #{owner}"
end
end
# This class is used to create sets of commands. Commands can be imported from
# different sets, aliased, removed, etc.
class CommandSet
include Enumerable
include Pry::Helpers::BaseHelpers
attr_reader :helper_module
# @param [Array<Commandset>] imported_sets
# Sets which will be imported automatically
# @yield Optional block run to define commands
def initialize(*imported_sets, &block)
@commands = {}
@helper_module = Module.new
import(*imported_sets)
instance_eval(&block) if block
end
# Defines a new Pry command.
# @param [String, Regexp] match The start of invocations of this command.
# @param [String] description A description of the command.
# @param [Hash] options The optional configuration parameters.
# @option options [Boolean] :keep_retval Whether or not to use return value
# of the block for return of `command` or just to return `nil`
# (the default).
# @option options [Boolean] :interpolate Whether string #{} based
# interpolation is applied to the command arguments before
# executing the command. Defaults to true.
# @option options [String] :listing The listing name of the
# command. That is the name by which the command is looked up by
# help and by show-source. Necessary for commands with regex matches.
# @option options [Boolean] :use_prefix Whether the command uses
# `Pry.config.command_prefix` prefix (if one is defined). Defaults
# to true.
# @option options [Boolean] :shellwords Whether the command's arguments
# should be split using Shellwords instead of just split on spaces.
# Defaults to true.
# @yield The action to perform. The parameters in the block
# determines the parameters the command will receive. All
# parameters passed into the block will be strings. Successive
# command parameters are separated by whitespace at the Pry prompt.
# @example
# MyCommands = Pry::CommandSet.new do
# command "greet", "Greet somebody" do |name|
# puts "Good afternoon #{name.capitalize}!"
# end
# end
#
# # From pry:
# # pry(main)> pry_instance.commands = MyCommands
# # pry(main)> greet john
# # Good afternoon John!
# # pry(main)> help greet
# # Greet somebody
# @example Regexp command
# MyCommands = Pry::CommandSet.new do
# command(
# /number-(\d+)/, "number-N regex command", :listing => "number"
# ) do |num, name|
# puts "hello #{name}, nice number: #{num}"
# end
# end
#
# # From pry:
# # pry(main)> pry_instance.commands = MyCommands
# # pry(main)> number-10 john
# # hello john, nice number: 10
# # pry(main)> help number
# # number-N regex command
def block_command(match, description = "No description.", options = {}, &block)
if description.is_a?(Hash)
options = description
description = "No description."
end
options = Pry::Command.default_options(match).merge!(options)
@commands[match] = Pry::BlockCommand.subclass(
match, description, options, helper_module, &block
)
end
alias command block_command
# Defines a new Pry command class.
#
# @param [String, Regexp] match The start of invocations of this command.
# @param [String] description A description of the command.
# @param [Hash] options The optional configuration parameters, see {#command}
# @yield The class body's definition.
#
# @example
# Pry::Commands.create_command "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
# if opts.present?(:u) && opts.present?(:d)
# raise Pry::CommandError, "-u and -d makes no sense"
# end
# result = args.join(" ")
# result.downcase! if opts.present?(:downcase)
# result.upcase! if opts.present?(:upcase)
# output.puts result
# end
# end
#
def create_command(match, description = "No description.", options = {}, &block)
if description.is_a?(Hash)
options = description
description = "No description."
end
options = Pry::Command.default_options(match).merge!(options)
@commands[match] = Pry::ClassCommand.subclass(
match, description, options, helper_module, &block
)
@commands[match].class_eval(&block)
@commands[match]
end
def each(&block)
@commands.each(&block)
end
# Removes some commands from the set
# @param [Array<String>] searches the matches or listings of the commands
# to remove
def delete(*searches)
searches.each do |search|
cmd = find_command_by_match_or_listing(search)
@commands.delete cmd.match
end
end
# Imports all the commands from one or more sets.
# @param [Array<CommandSet>] sets Command sets, all of the commands of which
# will be imported.
# @return [Pry::CommandSet] Returns the receiver (a command set).
def import(*sets)
sets.each do |set|
@commands.merge! set.to_hash
helper_module.send :include, set.helper_module
end
self
end
# Imports some commands from a set
# @param [CommandSet] set Set to import commands from
# @param [Array<String>] matches Commands to import
# @return [Pry::CommandSet] Returns the receiver (a command set).
def import_from(set, *matches)
helper_module.send :include, set.helper_module
matches.each do |match|
cmd = set.find_command_by_match_or_listing(match)
@commands[cmd.match] = cmd
end
self
end
# @param [String, Regexp] match_or_listing The match or listing of a command.
# of the command to retrieve.
# @return [Command] The command object matched.
def find_command_by_match_or_listing(match_or_listing)
cmd = (@commands[match_or_listing] ||
Pry::Helpers::BaseHelpers.find_command(match_or_listing, @commands))
cmd || raise(ArgumentError, "cannot find a command: '#{match_or_listing}'")
end
# Aliases a command
# @param [String, Regex] match The match of the alias (can be a regex).
# @param [String] action The action to be performed (typically
# another command).
# @param [Hash] options The optional configuration parameters,
# accepts the same as the `command` method, but also allows the
# command description to be passed this way too as `:desc`
# @example Creating an alias for `ls -M`
# Pry.config.commands.alias_command "lM", "ls -M"
# @example Pass explicit description (overriding default).
# Pry.config.commands.alias_command "lM", "ls -M", :desc => "cutiepie"
def alias_command(match, action, options = {})
(cmd = find_command(action)) || raise("command: '#{action}' not found")
original_options = cmd.options.dup
options = original_options.merge!(
desc: "Alias for `#{action}`",
listing: match.is_a?(String) ? match : match.inspect
).merge!(options)
# ensure default description is used if desc is nil
desc = options.delete(:desc).to_s
c = block_command match, desc, options do |*args|
run action, *args
end
# TODO: untested. What's this about?
c.class_eval do
define_method(:complete) do |input|
cmd.new(context).complete(input)
end
end
c.group "Aliases"
c
end
# Rename a command. Accepts either match or listing for the search.
#
# @param [String, Regexp] new_match The new match for the command.
# @param [String, Regexp] search The command's current match or listing.
# @param [Hash] options The optional configuration parameters,
# accepts the same as the `command` method, but also allows the
# command description to be passed this way too.
# @example Renaming the `ls` command and changing its description.
# Pry.config.commands.rename "dir", "ls", :description => "DOS friendly ls"
def rename_command(new_match, search, options = {})
cmd = find_command_by_match_or_listing(search)
options = {
listing: new_match,
description: cmd.description
}.merge!(options)
@commands[new_match] = cmd.dup
@commands[new_match].match = new_match
@commands[new_match].description = options.delete(:description)
@commands[new_match].options.merge!(options)
@commands.delete(cmd.match)
end
# Sets or gets the description for a command (replacing the old
# description). Returns current description if no description
# parameter provided.
# @param [String, Regexp] search The command match.
# @param [String?] description (nil) The command description.
# @example Setting
# MyCommands = Pry::CommandSet.new do
# desc "help", "help description"
# end
# @example Getting
# Pry.config.commands.desc "amend-line"
def desc(search, description = nil)
cmd = find_command_by_match_or_listing(search)
return cmd.description unless description
cmd.description = description
end
# @return [Array]
# The list of commands provided by the command set.
def list_commands
@commands.keys
end
alias keys list_commands
def to_hash
@commands.dup
end
alias to_h to_hash
# Find a command that matches the given line
# @param [String] pattern The line that might be a command invocation
# @return [Pry::Command, nil]
def [](pattern)
commands = @commands.values.select do |command|
command.matches?(pattern)
end
commands.max_by { |command| command.match_score(pattern) }
end
alias find_command []
#
# Re-assign the command found at _pattern_ with _command_.
#
# @param [Regexp, String] pattern
# The command to add or replace(found at _pattern_).
#
# @param [Pry::Command] command
# The command to add.
#
# @return [Pry::Command]
# Returns the new command (matched with "pattern".)
#
# @example
# Pry.config.commands["help"] = MyHelpCommand
#
def []=(pattern, command)
if command.equal?(nil)
@commands.delete(pattern)
return
end
unless command.is_a?(Class) && command < Pry::Command
raise TypeError, "command is not a subclass of Pry::Command"
end
bind_command_to_pattern = pattern != command.match
if bind_command_to_pattern
command_copy = command.dup
command_copy.match = pattern
@commands[pattern] = command_copy
else
@commands[pattern] = command
end
end
#
# Add a command to set.
#
# @param [Command] command
# a subclass of Pry::Command.
#
def add_command(command)
self[command.match] = command
end
# Find the command that the user might be trying to refer to.
# @param [String] search The user's search.
# @return [Pry::Command?]
def find_command_for_help(search)
find_command(search) ||
(begin
find_command_by_match_or_listing(search)
rescue ArgumentError
nil
end)
end
# Is the given line a command invocation?
# @param [String] val
# @return [Boolean]
def valid_command?(val)
!!find_command(val)
end
# Process the given line to see whether it needs executing as a command.
# @param [String] val The line to execute
# @param [Hash] context The context to execute the commands with
# @return [CommandSet::Result]
def process_line(val, context = {})
if (command = find_command(val))
context = context.merge(command_set: self)
retval = command.new(context).process_line(val)
Result.new(true, retval)
else
Result.new(false)
end
end
# Generate completions for the user's search.
# @param [String] search The line to search for
# @param [Hash] context The context to create the command with
# @return [Array<String>]
def complete(search, context = {})
if (command = find_command(search))
command.new(context).complete(search)
else
keys = @commands.keys.select do |key|
key.is_a?(String) && key.start_with?(search)
end
keys.map { |key| key + " " }
end
end
private
# Defines helpers methods for this command sets.
# Those helpers are only defined in this command set.
#
# @yield A block defining helper methods
# @example
# helpers do
# def hello
# puts "Hello!"
# end
#
# include OtherModule
# end
def helpers(&block)
helper_module.class_eval(&block)
end
end
# Wraps the return result of process_commands, indicates if the
# result IS a command and what kind of command (e.g void)
class Result
attr_reader :retval
def initialize(is_command, retval = nil)
@is_command = is_command
@retval = retval
end
# Is the result a command?
# @return [Boolean]
def command?
@is_command
end
# Is the result a command and if it is, is it a void command?
# (one that does not return a value)
# @return [Boolean]
def void_command?
retval == Command::VOID_VALUE
end
end
end
@@ -0,0 +1,36 @@
# frozen_string_literal: true
class Pry
# CommandState is a data structure to hold per-command state.
#
# Pry commands can store arbitrary state here. This state persists between
# subsequent command invocations. All state saved here is unique to the
# command.
#
# @since v0.13.0
# @api private
class CommandState
def self.default
@default ||= new
end
def initialize
@command_state = {}
end
def state_for(command_class)
@command_state[command_class] ||= command_struct(command_class)
end
def reset(command_class)
@command_state[command_class] = command_struct(command_class)
end
private
def command_struct(command_class)
Struct.new(:command, *command_class.command_options[:state])
.new(command: command_class)
end
end
end
@@ -0,0 +1,103 @@
# frozen_string_literal: true
class Pry
class Command
class AmendLine < Pry::ClassCommand
match(/amend-line(?: (-?\d+)(?:\.\.(-?\d+))?)?/)
group 'Editing'
description 'Amend a line of input in multi-line mode.'
command_options interpolate: false, listing: 'amend-line'
banner <<-'BANNER'
Amend a line of input in multi-line mode. `amend-line N`, where the N represents
line to replace. Can also specify a range of lines using `amend-line N..M`
syntax. Passing "!" as replacement content deletes the line(s) instead.
amend-line 1 puts 'new' # replace line 1
amend-line 1..4 ! # delete lines 1..4
amend-line 3 >puts 'bye' # insert before line 3
amend-line puts 'appended' # no line number modifies immediately preceding line
BANNER
def process
raise CommandError, "No input to amend." if eval_string.empty?
eval_string.replace(amend_input)
run "fix-indent"
run "show-input"
end
private
# @return [String] A new string with the amendments applied to it.
def amend_input
input_array = eval_string.each_line.to_a
if arg_string == "!"
delete_from_array(input_array, line_range)
elsif arg_string.start_with?(">")
insert_into_array(input_array, line_range)
else
replace_in_array(input_array, line_range)
end
input_array.join
end
def delete_from_array(array, range)
array.slice!(range)
end
def insert_into_array(array, range)
insert_slot = Array(range).first
array.insert(insert_slot, arg_string[1..-1] << "\n")
end
def replace_in_array(array, range)
array[range] = arg_string + "\n"
end
# @return [Fixnum] The number of lines currently in `eval_string` (the
# input buffer)
def line_count
eval_string.lines.count
end
# Returns the (one-indexed) start and end lines given by the user.
# The lines in this range will be affected by the `amend-line`.
# Returns `nil` if no lines were specified by the user.
# @return [Array<Fixnum>, nil]
def start_and_end_line_number
start_line_number, end_line_number = args
end_line_number ||= start_line_number.to_i
[start_line_number.to_i, end_line_number.to_i] if start_line_number
end
# Takes two numbers that are 1-indexed, and returns a range (or
# number) that is 0-indexed. 1-indexed means the first element is
# identified by 1 rather than by 0 (as is the case for Ruby arrays).
# @param [Fixnum] start_line_number One-indexed number.
# @param [Fixnum] end_line_number One-indexed number.
# @return [Range] The zero-indexed range.
def zero_indexed_range_from_one_indexed_numbers(start_line_number, end_line_number)
# FIXME: one_index_number is a horrible name for this method
one_index_number(start_line_number)..one_index_number(end_line_number)
end
# The lines (or line) that will be modified by the `amend-line`.
# @return [Range, Fixnum] The lines or line.
def line_range
start_line_number, end_line_number = start_and_end_line_number
if start_line_number
zero_indexed_range_from_one_indexed_numbers(start_line_number,
end_line_number)
else
line_count - 1
end
end
end
Pry::Commands.add_command(Pry::Command::AmendLine)
end
end
@@ -0,0 +1,24 @@
# frozen_string_literal: true
class Pry
class Command
class Bang < Pry::ClassCommand
match(/^\s*!\s*$/)
group 'Editing'
description 'Clear the input buffer.'
command_options use_prefix: false, listing: '!'
banner <<-'BANNER'
Clear the input buffer. Useful if the parsing process goes wrong and you get
stuck in the read loop.
BANNER
def process
output.puts 'Input buffer cleared!'
eval_string.replace('')
end
end
Pry::Commands.add_command(Pry::Command::Bang)
end
end
@@ -0,0 +1,21 @@
# frozen_string_literal: true
class Pry
class Command
class BangPry < Pry::ClassCommand
match '!pry'
group 'Navigating Pry'
description 'Start a Pry session on current self.'
banner <<-'BANNER'
Start a Pry session on current self. Also works mid multi-line expression.
BANNER
def process
target.pry
end
end
Pry::Commands.add_command(Pry::Command::BangPry)
end
end
@@ -0,0 +1,70 @@
# frozen_string_literal: true
class Pry
class Command
class Cat < Pry::ClassCommand
match 'cat'
group 'Input and Output'
description "Show code from a file, Pry's input buffer, or the last exception."
banner <<-'BANNER'
Usage: cat FILE
cat --ex [STACK_INDEX]
cat --in [INPUT_INDEX_OR_RANGE]
`cat` is capable of showing part or all of a source file, the context of the
last exception, or an expression from Pry's input history.
`cat --ex` defaults to showing the lines surrounding the location of the last
exception. Invoking it more than once travels up the exception's backtrace, and
providing a number shows the context of the given index of the backtrace.
BANNER
def options(opt)
opt.on :ex, "Show the context of the last exception",
optional_argument: true, as: Integer
opt.on :i, :in, "Show one or more entries from Pry's expression history",
optional_argument: true, as: Range, default: -5..-1
opt.on :s, :start, "Starting line (defaults to the first line)",
optional_argument: true, as: Integer
opt.on :e, :end, "Ending line (defaults to the last line)",
optional_argument: true, as: Integer
opt.on :l, :'line-numbers', "Show line numbers"
opt.on :t, :type, "The file type for syntax highlighting " \
"(e.g., 'ruby' or 'python')",
argument: true, as: Symbol
end
def process
output =
if opts.present?(:ex)
ExceptionFormatter.new(
pry_instance.last_exception, pry_instance, opts
).format
elsif opts.present?(:in)
InputExpressionFormatter.new(pry_instance.input_ring, opts).format
else
FileFormatter.new(args.first, pry_instance, opts).format
end
pry_instance.pager.page output
end
def complete(search)
super | load_path_completions
end
def load_path_completions
$LOAD_PATH.flat_map do |path|
Dir[path + '/**/*'].map do |f|
next if File.directory?(f)
f.sub!(path + '/', '')
end
end
end
end
Pry::Commands.add_command(Pry::Command::Cat)
end
end
@@ -0,0 +1,32 @@
# frozen_string_literal: true
class Pry
class Command
class Cat
class AbstractFormatter
include Pry::Helpers::CommandHelpers
include Pry::Helpers::BaseHelpers
private
def decorate(content)
content.code_type = code_type
content.between(*between_lines)
.with_line_numbers(use_line_numbers?).highlighted
end
def code_type
opts[:type] || :ruby
end
def use_line_numbers?
opts.present?(:'line-numbers') || opts.present?(:ex)
end
def between_lines
[opts[:start] || 1, opts[:end] || -1]
end
end
end
end
end
@@ -0,0 +1,90 @@
# frozen_string_literal: true
class Pry
class Command
class Cat
class ExceptionFormatter < AbstractFormatter
attr_reader :ex
attr_reader :opts
attr_reader :pry_instance
include Pry::Helpers::Text
def initialize(exception, pry_instance, opts)
@ex = exception
@opts = opts
@pry_instance = pry_instance
end
def format
check_for_errors
set_file_and_dir_locals(
backtrace_file, pry_instance, pry_instance.current_context
)
code = decorate(
Pry::Code.from_file(backtrace_file)
.between(*start_and_end_line_for_code_window)
.with_marker(backtrace_line)
)
"#{header}#{code}"
end
private
def code_window_size
pry_instance.config.default_window_size || 5
end
def backtrace_level
@backtrace_level ||=
begin
bl =
if opts[:ex].nil?
ex.bt_index
else
ex.bt_index = absolute_index_number(opts[:ex], ex.backtrace.size)
end
increment_backtrace_level
bl
end
end
def increment_backtrace_level
ex.inc_bt_index
end
def backtrace_file
Array(ex.bt_source_location_for(backtrace_level)).first
end
def backtrace_line
Array(ex.bt_source_location_for(backtrace_level)).last
end
def check_for_errors
raise CommandError, "No exception found." unless ex
return if backtrace_file
raise CommandError, "The given backtrace level is out of bounds."
end
def start_and_end_line_for_code_window
start_line = backtrace_line - code_window_size
start_line = 1 if start_line < 1
[start_line, backtrace_line + code_window_size]
end
def header
unindent(
"#{bold 'Exception:'} #{ex.class}: #{ex.message}\n" \
"--\n" \
"#{bold('From:')} #{backtrace_file}:#{backtrace_line} @ " \
"#{bold("level: #{backtrace_level}")} of backtrace " \
"(of #{ex.backtrace.size - 1}).\n\n"
)
end
end
end
end
end
@@ -0,0 +1,77 @@
# frozen_string_literal: true
class Pry
class Command
class Cat
class FileFormatter < AbstractFormatter
attr_reader :file_with_embedded_line
attr_reader :opts
attr_reader :pry_instance
def initialize(file_with_embedded_line, pry_instance, opts)
unless file_with_embedded_line
raise CommandError, "Must provide a filename, --in, or --ex."
end
@file_with_embedded_line = file_with_embedded_line
@opts = opts
@pry_instance = pry_instance
@code_from_file = Pry::Code.from_file(file_name)
end
def format
set_file_and_dir_locals(file_name, pry_instance, pry_instance.current_context)
decorate(@code_from_file)
end
def file_and_line
file_name, line_num = file_with_embedded_line.split(%r{:(?!/|\\)})
[file_name, line_num ? line_num.to_i : nil]
end
private
def file_name
file_and_line.first
end
def line_number
file_and_line.last
end
def code_window_size
pry_instance.config.default_window_size || 7
end
def decorate(content)
if line_number
super(content.around(line_number, code_window_size))
else
super
end
end
def code_type
opts[:type] || detect_code_type_from_file(file_name)
end
def detect_code_type_from_file(file_name)
code_type = @code_from_file.code_type
if code_type == :unknown
name = File.basename(file_name).split('.', 2).first
case name
when "Rakefile", "Gemfile"
:ruby
else
:text
end
else
code_type
end
end
end
end
end
end
@@ -0,0 +1,48 @@
# frozen_string_literal: true
class Pry
class Command
class Cat
class InputExpressionFormatter < AbstractFormatter
attr_accessor :input_expressions
attr_accessor :opts
def initialize(input_expressions, opts)
@input_expressions = input_expressions
@opts = opts
end
def format
raise CommandError, "No input expressions!" if numbered_input_items.empty?
if numbered_input_items.length > 1
content = ''
numbered_input_items.each do |i, s|
content += "#{Helpers::Text.bold(i.to_s)}:\n"
content += decorate(Pry::Code(s).with_indentation(2)).to_s
end
content
else
decorate(Pry::Code(selected_input_items.first))
end
end
private
def selected_input_items
input_expressions[normalized_expression_range] || []
end
def numbered_input_items
@numbered_input_items ||= normalized_expression_range.zip(selected_input_items)
.reject { |_, s| s.nil? || s == "" }
end
def normalized_expression_range
absolute_index_range(opts[:i], input_expressions.count)
end
end
end
end
end
@@ -0,0 +1,48 @@
# frozen_string_literal: true
class Pry
class Command
class Cd < Pry::ClassCommand
match 'cd'
group 'Context'
description 'Move into a new context (object or scope).'
banner <<-'BANNER'
Usage: cd [OPTIONS] [--help]
Move into new context (object or scope). As in UNIX shells use `cd ..` to go
back, `cd /` to return to Pry top-level and `cd -` to toggle between last two
scopes. Complex syntax (e.g `cd ../@x/@y`) also supported.
cd @x
cd ..
cd /
cd -
https://github.com/pry/pry/wiki/State-navigation#wiki-Changing_scope
BANNER
command_options state: %i[old_stack]
def process
state.old_stack ||= []
if arg_string.strip == "-"
unless state.old_stack.empty?
pry_instance.binding_stack, state.old_stack =
state.old_stack, pry_instance.binding_stack
end
else
stack = ObjectPath.new(arg_string, pry_instance.binding_stack).resolve
if stack && stack != pry_instance.binding_stack
state.old_stack = pry_instance.binding_stack
pry_instance.binding_stack = stack
end
end
end
end
Pry::Commands.add_command(Pry::Command::Cd)
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
class Pry
class Command
class ChangeInspector < Pry::ClassCommand
match 'change-inspector'
group 'Input and Output'
description 'Change the current inspector proc.'
command_options argument_required: true
banner <<-BANNER
Usage: change-inspector NAME
Change the proc used to print return values. See list-inspectors for a list
of available procs and a short description of what each one does.
BANNER
def process(inspector)
unless inspector_map.key?(inspector)
raise Pry::CommandError, "'#{inspector}' isn't a known inspector!"
end
pry_instance.print = inspector_map[inspector][:value]
output.puts "Switched to the '#{inspector}' inspector!"
end
private
def inspector_map
Pry::Inspector::MAP
end
Pry::Commands.add_command(self)
end
end
end
@@ -0,0 +1,51 @@
# frozen_string_literal: true
class Pry
class Command
class ChangePrompt < Pry::ClassCommand
match 'change-prompt'
group 'Input and Output'
description 'Change the current prompt.'
command_options argument_required: true
banner <<-BANNER
Usage: change-prompt [OPTIONS] [NAME]
Change the current prompt.
BANNER
def options(opt)
opt.on(:l, :list, 'List the available prompts')
end
def process(prompt)
if opts.present?(:l)
list_prompts
else
change_prompt(prompt)
end
end
private
def list_prompts
prompts = Pry::Prompt.all.map do |name, prompt|
"#{bold(name)}#{red(' (selected)') if pry_instance.prompt == prompt}\n" +
prompt.description
end
output.puts(prompts.join("\n" * 2))
end
def change_prompt(prompt)
if Pry::Prompt[prompt]
pry_instance.prompt = Pry::Prompt[prompt]
else
raise Pry::CommandError,
"'#{prompt}' isn't a known prompt. Run `change-prompt --list` " \
"to see the list of known prompts."
end
end
Pry::Commands.add_command(self)
end
end
end
@@ -0,0 +1,20 @@
# frozen_string_literal: true
class Pry
class Command
class ClearScreen < Pry::ClassCommand
match 'clear-screen'
group 'Input and Output'
description 'Clear the contents of the screen/window Pry is running in.'
def process
if Pry::Helpers::Platform.windows?
pry_instance.config.system.call(pry_instance.output, 'cls', pry_instance)
else
pry_instance.config.system.call(pry_instance.output, 'clear', pry_instance)
end
end
Pry::Commands.add_command(self)
end
end
end
@@ -0,0 +1,182 @@
# frozen_string_literal: true
class Pry
class Command
class CodeCollector
include Helpers::CommandHelpers
attr_reader :args
attr_reader :opts
attr_reader :pry_instance
# The name of the explicitly given file (if any).
attr_accessor :file
class << self
attr_accessor :input_expression_ranges
attr_accessor :output_result_ranges
end
@input_expression_ranges = []
@output_result_ranges = []
def initialize(args, opts, pry_instance)
@args = args
@opts = opts
@pry_instance = pry_instance
end
# Add the `--lines`, `-o`, `-i`, `-s`, `-d` options.
def self.inject_options(opt)
@input_expression_ranges = []
@output_result_ranges = []
opt.on :l, :lines, "Restrict to a subset of lines. Takes a line number " \
"or range",
optional_argument: true, as: Range, default: 1..-1
opt.on :o, :out, "Select lines from Pry's output result history. " \
"Takes an index or range",
optional_argument: true, as: Range, default: -5..-1 do |r|
output_result_ranges << (r || (-5..-1))
end
opt.on :i, :in, "Select lines from Pry's input expression history. " \
"Takes an index or range",
optional_argument: true, as: Range, default: -5..-1 do |r|
input_expression_ranges << (r || (-5..-1))
end
opt.on :s, :super, "Select the 'super' method. Can be repeated to " \
"traverse the ancestors",
as: :count
opt.on :d, :doc, "Select lines from the code object's documentation"
end
# The content (i.e code/docs) for the selected object.
# If the user provided a bare code object, it returns the source.
# If the user provided the `-i` or `-o` switches, it returns the
# selected input/output lines joined as a string. If the user used
# `-d CODE_OBJECT` it returns the docs for that code object.
#
# @return [String]
def content
@content ||=
begin
if bad_option_combination?
raise CommandError,
"Only one of --out, --in, --doc and CODE_OBJECT may " \
"be specified."
end
content = if opts.present?(:o)
pry_output_content
elsif opts.present?(:i)
pry_input_content
elsif opts.present?(:d)
code_object_doc
else
code_object_source_or_file
end
restrict_to_lines(content, line_range)
end
end
# The code object
#
# @return [Pry::WrappedModule, Pry::Method, Pry::Command]
def code_object
Pry::CodeObject.lookup(obj_name, pry_instance, super: opts[:super])
end
# Given a string and a range, return the `range` lines of that
# string.
#
# @param [String] content
# @param [Range, Fixnum] range
# @return [String] The string restricted to the given range
def restrict_to_lines(content, range)
Array(content.lines.to_a[range]).join
end
# The selected `pry_instance.output_ring` as a string, as specified by
# the `-o` switch.
#
# @return [String]
def pry_output_content
pry_array_content_as_string(
pry_instance.output_ring,
self.class.output_result_ranges,
&:pretty_inspect
)
end
# The selected `pry_instance.input_ring` as a string, as specified by
# the `-i` switch.
#
# @return [String]
def pry_input_content
pry_array_content_as_string(
pry_instance.input_ring, self.class.input_expression_ranges
) { |v| v }
end
# The line range passed to `--lines`, converted to a 0-indexed range.
def line_range
opts.present?(:lines) ? one_index_range_or_number(opts[:lines]) : 0..-1
end
# Name of the object argument
def obj_name
@obj_name ||= args.empty? ? "" : args.join(" ")
end
private
def bad_option_combination?
[opts.present?(:in), opts.present?(:out),
!args.empty?].count(true) > 1
end
def pry_array_content_as_string(array, ranges)
all = ''
ranges.each do |range|
if convert_to_range(range).first == 0
raise CommandError, "Minimum value for range is 1, not 0."
end
ranged_array = Array(array[range]) || []
ranged_array.compact.each { |v| all += yield(v) }
end
all
end
def code_object_doc
(code_object && code_object.doc) || could_not_locate(obj_name)
end
def code_object_source_or_file
(code_object && code_object.source) || file_content
end
def file_content
if File.exist?(obj_name)
# Set the file accessor.
self.file = obj_name
File.read(obj_name)
else
could_not_locate(obj_name)
end
end
def could_not_locate(name)
raise CommandError, "Cannot locate: #{name}!"
end
def convert_to_range(range)
return range if range.is_a?(Range)
(range..range)
end
end
end
end
@@ -0,0 +1,31 @@
# frozen_string_literal: true
class Pry
class Command
class DisablePry < Pry::ClassCommand
match 'disable-pry'
group 'Navigating Pry'
description 'Stops all future calls to pry and exits the current session.'
banner <<-'BANNER'
Usage: disable-pry
After this command is run any further calls to pry will immediately return `nil`
without interrupting the flow of your program. This is particularly useful when
you've debugged the problem you were having, and now wish the program to run to
the end.
As alternatives, consider using `exit!` to force the current Ruby process
to quit immediately; or using `edit -p` to remove the `binding.pry`
from the code.
BANNER
def process
ENV['DISABLE_PRY'] = 'true'
pry_instance.run_command "exit"
end
end
Pry::Commands.add_command(Pry::Command::DisablePry)
end
end
@@ -0,0 +1,101 @@
# frozen_string_literal: true
class Pry
Pry::Commands.instance_eval do
command(%r{!s/(.*?)/(.*?)}, "") do |source, dest|
eval_string.gsub!(/#{source}/) { dest }
run "show-input"
end
command "east-coker", "" do
txt = %(
--
Now the light falls
Across the open field, leaving the deep lane
Shuttered with branches, dark in the afternoon,
Where you lean against a bank while a van passes,
And the deep lane insists on the direction
Into the village, in the electric heat
Hypnotised. In a warm haze the sultry light
Is absorbed, not refracted, by grey stone.
The dahlias sleep in the empty silence.
Wait for the early owl.
-- T.S Eliot
)
output.puts txt
txt
end
command "cohen-poem", "" do
txt = %(
--
When this American woman,
whose thighs are bound in casual red cloth,
comes thundering past my sitting place
like a forest-burning Mongol tribe,
the city is ravished
and brittle buildings of a hundred years
splash into the street;
and my eyes are burnt
for the embroidered Chinese girls,
already old,
and so small between the thin pines
on these enormous landscapes,
that if you turn your head
they are lost for hours.
-- Leonard Cohen
)
output.puts txt
txt
end
command "pessoa-poem", "" do
output.puts <<-TEXT
--
I've gone to bed with every feeling,
I've been the pimp of every emotion,
All felt sensations have bought me drinks,
I've traded glances with every motive for every act,
I've held hands with every urge to depart,
..
Rage, foam, the vastness that doesn't fit in my handkerchief,
The dog in heat howling in the night,
The pond from the farm going in circles around my insomnia,
The woods as they were, on our late-afternoon walks, the rose,
The indifferent tuft of hair, the moss, the pines,
The rage of not containing all this, not retaining all this,
O abstract hunger for things, impotent libido for moments,
Intellectual orgy of feeling life!
-- Fernando Pessoa
TEXT
end
command "test-ansi", "" do
prev_color = pry_instance.config.color
pry_instance.config.color = true
picture = unindent <<-'OUTPUT'.gsub(/[[:alpha:]!]/) { |s| red(s) }
____ _______________________
/ \ | A W G |
/ O O \ | N I O N ! |
| | | S S R I ! |
\ \__/ / __| I K ! |
\____/ \________________________|
OUTPUT
move_up =
if Helpers::Platform.windows_ansi?
proc { |n| "\e[#{n}F" }
else
proc { |n| "\e[#{n}A\e[0G" }
end
output.puts "\n" * 6
output.puts picture.lines.map(&:chomp).reverse.join(move_up[1])
output.puts "\n" * 6
output.puts "** ENV['TERM'] is #{ENV['TERM']} **\n\n"
pry_instance.config.color = prev_color
end
end
end
@@ -0,0 +1,225 @@
# frozen_string_literal: true
class Pry
class Command
class Edit < Pry::ClassCommand
match 'edit'
group 'Editing'
description 'Invoke the default editor on a file.'
banner <<-'BANNER'
Usage: edit [--no-reload|--reload|--patch] [--line LINE] [--temp|--ex|FILE[:LINE]|OBJECT|--in N]
Open a text editor. When no FILE is given, edits the pry input buffer.
When a method/module/command is given, the code is opened in an editor.
Ensure `Pry.config.editor` or `pry_instance.config.editor` is set to your editor of choice.
edit sample.rb edit -p MyClass#my_method
edit sample.rb --line 105 edit MyClass
edit MyClass#my_method edit --ex
edit --method edit --ex -p
https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_command
BANNER
command_options state: %i[dynamical_ex_file]
def options(opt)
opt.on :e, :ex, "Open the file that raised the most recent exception " \
"(_ex_.file)",
optional_argument: true, as: Integer
opt.on :i, :in, "Open a temporary file containing the Nth input " \
"expression. N may be a range",
optional_argument: true, as: Range, default: -1..-1
opt.on :t, :temp, "Open an empty temporary file"
opt.on :l, :line, "Jump to this line in the opened file",
argument: true, as: Integer
opt.on :n, :"no-reload", "Don't automatically reload the edited file"
opt.on :c, :current, "Open the current __FILE__ and at __LINE__ (as " \
"returned by `whereami`)"
opt.on :r, :reload, "Reload the edited code immediately (default for " \
"ruby files)"
opt.on :p, :patch, "Instead of editing the object's file, try to edit " \
"in a tempfile and apply as a monkey patch"
opt.on :m, :method, "Explicitly edit the _current_ method (when " \
"inside a method context)."
end
def process
if bad_option_combination?
raise CommandError, "Only one of --ex, --temp, --in, --method and " \
"FILE may be specified."
end
if repl_edit?
# code defined in pry, eval'd within pry.
repl_edit
elsif runtime_patch?
# patch code without persisting changes, implies future changes are patches
apply_runtime_patch
else
# code stored in actual files, eval'd at top-level
file_edit
end
end
def repl_edit?
!opts.present?(:ex) && !opts.present?(:current) && !opts.present?(:method) &&
filename_argument.empty?
end
def repl_edit
content = Pry::Editor.new(pry_instance).edit_tempfile_with_content(
initial_temp_file_content,
initial_temp_file_content.lines.count
)
pry_instance.eval_string = content
Pry.history.push(content)
end
def file_based_exception?
opts.present?(:ex) && !opts.present?(:patch)
end
def runtime_patch?
!file_based_exception? &&
(opts.present?(:patch) ||
previously_patched?(code_object) ||
pry_method?(code_object))
end
def apply_runtime_patch
if patch_exception?
ExceptionPatcher.new(
pry_instance, state, file_and_line_for_current_exception
).perform_patch
elsif code_object.is_a?(Pry::Method)
code_object.redefine(
Pry::Editor.new(pry_instance).edit_tempfile_with_content(
code_object.source
)
)
else
raise NotImplementedError, "Cannot yet patch #{code_object} objects!"
end
end
def ensure_file_name_is_valid(file_name)
unless file_name
raise CommandError, "Cannot find a valid file for #{filename_argument}"
end
return unless not_a_real_file?(file_name)
raise CommandError, "#{file_name} is not a valid file name, cannot edit!"
end
def file_and_line_for_current_exception
FileAndLineLocator.from_exception(pry_instance.last_exception, opts[:ex].to_i)
end
def file_and_line
file_name, line =
if opts.present?(:current)
FileAndLineLocator.from_binding(target)
elsif opts.present?(:ex)
file_and_line_for_current_exception
elsif code_object
FileAndLineLocator.from_code_object(code_object, filename_argument)
else
# when file and line are passed as a single arg, e.g my_file.rb:30
FileAndLineLocator.from_filename_argument(filename_argument)
end
[file_name, opts.present?(:line) ? opts[:l].to_i : line]
end
def file_edit
file_name, line = file_and_line
ensure_file_name_is_valid(file_name)
Pry::Editor.new(pry_instance).invoke_editor(file_name, line, reload?(file_name))
set_file_and_dir_locals(file_name)
return unless reload?(file_name)
silence_warnings { load(file_name) }
end
def filename_argument
args.join(' ')
end
def code_object
@code_object ||=
!probably_a_file?(filename_argument) &&
Pry::CodeObject.lookup(filename_argument, pry_instance)
end
def pry_method?(code_object)
code_object.is_a?(Pry::Method) &&
code_object.pry_method?
end
def previously_patched?(code_object)
code_object.is_a?(Pry::Method) &&
Pry::Method::Patcher.code_for(code_object.source_location.first)
end
def patch_exception?
opts.present?(:ex) && opts.present?(:patch)
end
def bad_option_combination?
[
opts.present?(:ex), opts.present?(:temp),
opts.present?(:in), opts.present?(:method),
!filename_argument.empty?
].count(true) > 1
end
def input_expression
case opts[:i]
when Range
(pry_instance.input_ring[opts[:i]] || []).join
when Integer
pry_instance.input_ring[opts[:i]] || ""
else
raise Pry::CommandError, "Not a valid range: #{opts[:i]}"
end
end
def reloadable?
opts.present?(:reload) || opts.present?(:ex)
end
def never_reload?
opts.present?(:'no-reload') || pry_instance.config.disable_auto_reload
end
def reload?(file_name = "")
(reloadable? || file_name.end_with?(".rb")) && !never_reload?
end
def initial_temp_file_content
if opts.present?(:temp)
""
elsif opts.present?(:in)
input_expression
elsif eval_string.strip != ""
eval_string
else
pry_instance.input_ring.to_a.reverse_each.find { |x| x && x.strip != "" } || ""
end
end
def probably_a_file?(str)
[".rb", ".c", ".py", ".yml", ".gemspec"].include?(File.extname(str)) ||
str =~ %r{/|\\}
end
end
Pry::Commands.add_command(Pry::Command::Edit)
end
end
@@ -0,0 +1,29 @@
# frozen_string_literal: true
class Pry
class Command
class Edit
class ExceptionPatcher
attr_accessor :pry_instance
attr_accessor :state
attr_accessor :file_and_line
def initialize(pry_instance, state, exception_file_and_line)
@pry_instance = pry_instance
@state = state
@file_and_line = exception_file_and_line
end
# perform the patch
def perform_patch
file_name, = file_and_line
lines = state.dynamical_ex_file || File.read(file_name)
source = Pry::Editor.new(pry_instance).edit_tempfile_with_content(lines)
pry_instance.evaluate_ruby source
state.dynamical_ex_file = source.split("\n")
end
end
end
end
end
@@ -0,0 +1,47 @@
# frozen_string_literal: true
class Pry
class Command
class Edit
module FileAndLineLocator
class << self
def from_binding(target)
if target.respond_to?(:source_location)
target.source_location
else
target.eval("[__FILE__, __LINE__]")
end
end
def from_code_object(code_object, filename_argument)
unless File.exist?(code_object.source_file.to_s)
raise CommandError, "Cannot find a file for #{filename_argument}!"
end
[code_object.source_file, code_object.source_line]
end
def from_exception(exception, backtrace_level)
raise CommandError, "No exception found." if exception.nil?
file_name, line = exception.bt_source_location_for(backtrace_level)
raise CommandError, "Exception has no associated file." if file_name.nil?
if Pry.eval_path == file_name
raise CommandError, "Cannot edit exceptions raised in REPL."
end
[file_name, line]
end
# when file and line are passed as a single arg, e.g my_file.rb:30
def from_filename_argument(filename_argument)
f = File.expand_path(filename_argument)
l = f.sub!(/:(\d+)$/, "") ? Regexp.last_match(1).to_i : 1
[f, l]
end
end
end
end
end
end
@@ -0,0 +1,47 @@
# frozen_string_literal: true
class Pry
class Command
class Exit < Pry::ClassCommand
match 'exit'
group 'Navigating Pry'
description 'Pop the previous binding.'
command_options keep_retval: true
banner <<-'BANNER'
Usage: exit [OPTIONS] [--help]
Aliases: quit
Pop the previous binding (does NOT exit program). It can be useful to exit a
context with a user-provided value. For instance an exit value can be used to
determine program flow.
exit "pry this"
exit
https://github.com/pry/pry/wiki/State-navigation#wiki-Exit_with_value
BANNER
def process
if pry_instance.binding_stack.one?
pry_instance.run_command "exit-all #{arg_string}"
else
# otherwise just pop a binding and return user supplied value
process_pop_and_return
end
end
def process_pop_and_return
popped_object = pry_instance.binding_stack.pop.eval('self')
# return a user-specified value if given otherwise return the object
return target.eval(arg_string) unless arg_string.empty?
popped_object
end
end
Pry::Commands.add_command(Pry::Command::Exit)
Pry::Commands.alias_command 'quit', 'exit'
end
end
@@ -0,0 +1,33 @@
# frozen_string_literal: true
class Pry
class Command
class ExitAll < Pry::ClassCommand
match 'exit-all'
group 'Navigating Pry'
description 'End the current Pry session.'
banner <<-'BANNER'
Usage: exit-all [--help]
Aliases: !!@
End the current Pry session (popping all bindings and returning to caller).
Accepts optional return value.
BANNER
def process
# calculate user-given value
exit_value = target.eval(arg_string)
# clear the binding stack
pry_instance.binding_stack.clear
# break out of the repl loop
throw(:breakout, exit_value)
end
end
Pry::Commands.add_command(Pry::Command::ExitAll)
Pry::Commands.alias_command '!!@', 'exit-all'
end
end
@@ -0,0 +1,27 @@
# frozen_string_literal: true
class Pry
class Command
class ExitProgram < Pry::ClassCommand
match 'exit-program'
group 'Navigating Pry'
description 'End the current program.'
banner <<-'BANNER'
Usage: exit-program [--help]
Aliases: quit-program
!!!
End the current program.
BANNER
def process
Kernel.exit target.eval(arg_string).to_i
end
end
Pry::Commands.add_command(Pry::Command::ExitProgram)
Pry::Commands.alias_command 'quit-program', 'exit-program'
Pry::Commands.alias_command '!!!', 'exit-program'
end
end
@@ -0,0 +1,199 @@
# frozen_string_literal: true
class Pry
class Command
class FindMethod < Pry::ClassCommand
extend Pry::Helpers::BaseHelpers
match 'find-method'
group 'Context'
description 'Recursively search for a method within a Class/Module or ' \
'the current namespace.'
command_options shellwords: false
banner <<-'BANNER'
Usage: find-method [-n|-c] METHOD [NAMESPACE]
Recursively search for a method within a Class/Module or the current namespace.
Use the `-n` switch (the default) to search for methods whose name matches the
given regex. Use the `-c` switch to search for methods that contain the given
code.
# Find all methods whose name match /re/ inside
# the Pry namespace. Matches Pry#repl, etc.
find-method re Pry
# Find all methods that contain the code:
# output.puts inside the Pry namespace.
find-method -c 'output.puts' Pry
BANNER
def options(opt)
opt.on :n, :name, "Search for a method by name"
opt.on :c, :content, "Search for a method based on content in Regex form"
end
def process
return if args.empty?
klass = search_class
matches = opts.content? ? content_search(klass) : name_search(klass)
show_search_results(matches)
end
private
# @return [Regexp] The pattern to search for.
def pattern
@pattern ||= ::Regexp.new args[0]
end
# Output the result of the search.
#
# @param [Array] matches
def show_search_results(matches)
if matches.empty?
output.puts bold("No Methods Matched")
else
print_matches(matches)
end
end
# The class to search for methods.
# We only search classes, so if the search object is an
# instance, return its class. If no search object is given
# search `target_self`.
def search_class
klass = if args[1]
target.eval(args[1])
else
target_self
end
klass.is_a?(Module) ? klass : klass.class
end
# pretty-print a list of matching methods.
#
# @param [Array<Method>] matches
def print_matches(matches)
grouped = matches.group_by(&:owner)
order = grouped.keys.sort_by { |x| x.name || x.to_s }
order.each do |klass|
print_matches_for_class(klass, grouped)
end
end
# Print matched methods for a class
def print_matches_for_class(klass, grouped)
output.puts bold(klass.name)
grouped[klass].each do |method|
header = method.name_with_owner
output.puts header + additional_info(header, method)
end
end
# Return the matched lines of method source if `-c` is given or ""
# if `-c` was not given
def additional_info(header, method)
if opts.content?
': ' + colorize_code(matched_method_lines(header, method))
else
""
end
end
def matched_method_lines(header, method)
method.source.split(/\n/).select { |x| x =~ pattern }.join(
"\n#{' ' * header.length}"
)
end
# Run the given block against every constant in the provided namespace.
#
# @param [Module] klass The namespace in which to start the search.
# @param [Hash<Module,Boolean>] done The namespaces we've already visited (private)
# @yieldparam klass Each class/module in the namespace.
#
def recurse_namespace(klass, done = {}, &block)
return if !klass.is_a?(Module) || done[klass]
done[klass] = true
yield klass
klass.constants.each do |name|
next if klass.autoload?(name)
begin
const = Pry::Method.singleton_class_of(klass.const_get(name))
rescue RescuableException # rubocop:disable Lint/HandleExceptions
# constant loading is an inexact science at the best of times,
# this often happens when a constant was .autoload? but someone
# tried to load it. It's now not .autoload? but will still raise
# a NameError when you access it.
else
recurse_namespace(const, done, &block)
end
end
end
# Gather all the methods in a namespace that pass the given block.
#
# @param [Module] namespace The namespace in which to search.
# @yieldparam [Method] method The method to test
# @yieldreturn [Boolean]
# @return [Array<Method>]
#
def search_all_methods(namespace)
done = Hash.new { |h, k| h[k] = {} }
matches = []
recurse_namespace(namespace) do |klass|
methods = Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)
methods.each do |method|
next if done[method.owner][method.name]
done[method.owner][method.name] = true
matches << method if yield method
end
end
matches
end
# Search for all methods with a name that matches the given regex
# within a namespace.
#
# @param [Module] namespace The namespace to search
# @return [Array<Method>]
#
def name_search(namespace)
search_all_methods(namespace) do |meth|
meth.name =~ pattern
end
end
# Search for all methods who's implementation matches the given regex
# within a namespace.
#
# @param [Module] namespace The namespace to search
# @return [Array<Method>]
#
def content_search(namespace)
search_all_methods(namespace) do |meth|
begin
meth.source =~ pattern
rescue RescuableException
false
end
end
end
end
Pry::Commands.add_command(Pry::Command::FindMethod)
end
end
@@ -0,0 +1,23 @@
# frozen_string_literal: true
class Pry
class Command
class FixIndent < Pry::ClassCommand
match 'fix-indent'
group 'Input and Output'
description "Correct the indentation for contents of the input buffer"
banner <<-USAGE
Usage: fix-indent
USAGE
def process
indented_str = Pry::Indent.indent(eval_string)
pry_instance.eval_string = indented_str
end
end
Pry::Commands.add_command(Pry::Command::FixIndent)
end
end
@@ -0,0 +1,171 @@
# frozen_string_literal: true
class Pry
class Command
class Help < Pry::ClassCommand
match 'help'
group 'Help'
description 'Show a list of commands or information about a specific command.'
banner <<-'BANNER'
Usage: help [COMMAND]
With no arguments, help lists all the available commands along with their
descriptions. When given a command name as an argument, shows the help
for that command.
BANNER
# We only want to show commands that have descriptions, so that the
# easter eggs don't show up.
def visible_commands
visible = {}
commands.each do |key, command|
visible[key] = command if command.description && !command.description.empty?
end
visible
end
# Get a hash of available commands grouped by the "group" name.
def command_groups
visible_commands.values.group_by(&:group)
end
def process
if args.empty?
display_index(command_groups)
else
display_search(args.first)
end
end
# Display the index view, with headings and short descriptions per command.
#
# @param [Hash<String, Array<Commands>>] groups
def display_index(groups)
help_text = []
sorted_group_names(groups).each do |group_name|
commands = sorted_commands(groups[group_name])
help_text << help_text_for_commands(group_name, commands) if commands.any?
end
pry_instance.pager.page help_text.join("\n\n")
end
# Given a group name and an array of commands,
# return the help string for those commands.
#
# @param [String] name The group name.
# @param [Array<Pry::Command>] commands
# @return [String] The generated help string.
def help_text_for_commands(name, commands)
"#{bold(name.capitalize)}\n" + commands.map do |command|
" #{command.options[:listing].to_s.ljust(18)} " \
"#{command.description.capitalize}"
end.join("\n")
end
# @param [Hash] groups
# @return [Array<String>] An array of sorted group names.
def sorted_group_names(groups)
groups.keys.sort_by(&method(:group_sort_key))
end
# Sort an array of commands by their `listing` name.
#
# @param [Array<Pry::Command>] commands The commands to sort
# @return [Array<Pry::Command>] commands sorted by listing name.
def sorted_commands(commands)
commands.sort_by { |command| command.options[:listing].to_s }
end
# Display help for an individual command or group.
#
# @param [String] search The string to search for.
def display_search(search)
if (command = command_set.find_command_for_help(search))
display_command(command)
else
display_filtered_search_results(search)
end
end
# Display help for a searched item, filtered first by group
# and if that fails, filtered by command name.
#
# @param [String] search The string to search for.
def display_filtered_search_results(search)
groups = search_hash(search, command_groups)
if !groups.empty?
display_index(groups)
else
display_filtered_commands(search)
end
end
# Display help for a searched item, filtered by group
#
# @param [String] search The string to search for.
def display_filtered_commands(search)
filtered = search_hash(search, visible_commands)
raise CommandError, "No help found for '#{args.first}'" if filtered.empty?
if filtered.size == 1
display_command(filtered.values.first)
else
display_index("'#{search}' commands" => filtered.values)
end
end
# Display help for an individual command.
#
# @param [Pry::Command] command
def display_command(command)
pry_instance.pager.page command.new.help
end
# Find a subset of a hash that matches the user's search term.
#
# If there's an exact match a Hash of one element will be returned,
# otherwise a sub-Hash with every key that matches the search will
# be returned.
#
# @param [String] search the search term
# @param [Hash] hash the hash to search
def search_hash(search, hash)
matching = {}
hash.each_pair do |key, value|
next unless key.is_a?(String)
return { key => value } if normalize(key) == normalize(search)
next unless normalize(key).start_with?(normalize(search))
matching[key] = value
end
matching
end
# Clean search terms to make it easier to search group names
#
# @param [String] key
# @return [String]
def normalize(key)
key.downcase.gsub(/pry\W+/, '')
end
def group_sort_key(group_name)
[
%w[
Help Context Editing Introspection Input_and_output Navigating_pry
Gems Basic Commands
].index(group_name.tr(' ', '_')) || 99, group_name
]
end
end
Pry::Commands.add_command(Pry::Command::Help)
end
end
@@ -0,0 +1,182 @@
# frozen_string_literal: true
class Pry
class Command
class Hist < Pry::ClassCommand
match 'hist'
group 'Editing'
description 'Show and replay Readline history.'
banner <<-'BANNER'
Usage: hist [--head|--tail]
hist --all
hist --head N
hist --tail N
hist --show START..END
hist --grep PATTERN
hist --clear
hist --replay START..END
hist --save [START..END] FILE
Aliases: history
Show and replay Readline history.
BANNER
def options(opt)
opt.on :a, :all, "Display all history"
opt.on :H, :head, "Display the first N items",
optional_argument: true, as: Integer
opt.on :T, :tail, "Display the last N items",
optional_argument: true, as: Integer
opt.on :s, :show, "Show the given range of lines",
optional_argument: true, as: Range
opt.on :G, :grep, "Show lines matching the given pattern",
argument: true, as: String
opt.on :c, :clear, "Clear the current session's history"
opt.on :r, :replay, "Replay a line or range of lines",
argument: true, as: Range
opt.on :save, "Save history to a file", argument: true, as: Range
opt.on :e, :'exclude-pry', "Exclude Pry commands from the history"
opt.on :n, :'no-numbers', "Omit line numbers"
end
def process
@history = find_history
@history = @history.between(opts[:show]) if opts.present?(:show)
@history = @history.grep(opts[:grep]) if opts.present?(:grep)
@history =
if opts.present?(:head)
@history.take_lines(1, opts[:head] || 10)
elsif opts.present?(:tail)
@history.take_lines(-(opts[:tail] || 10), opts[:tail] || 10)
else
@history
end
if opts.present?(:'exclude-pry')
@history = @history.reject do |loc|
command_set.valid_command?(loc.line)
end
end
if opts.present?(:save)
process_save
elsif opts.present?(:clear)
process_clear
elsif opts.present?(:replay)
process_replay
else
process_display
end
end
private
def process_display
@history = @history.with_line_numbers unless opts.present?(:'no-numbers')
pry_instance.pager.open do |pager|
@history.print_to_output(pager, true)
end
end
def process_save
case opts[:save]
when Range
@history = @history.between(opts[:save])
raise CommandError, "Must provide a file name." unless args.first
file_name = File.expand_path(args.first)
when String
file_name = File.expand_path(opts[:save])
end
output.puts "Saving history in #{file_name}..."
File.open(file_name, 'w') { |f| f.write(@history.raw) }
output.puts "History saved."
end
def process_clear
Pry.history.clear
output.puts "History cleared."
end
def process_replay
@history = @history.between(opts[:r])
replay_sequence = @history.raw
# If we met follow-up "hist" call, check for the "--replay" option
# presence. If "hist" command is called with other options, proceed
# further.
check_for_juxtaposed_replay(replay_sequence)
replay_sequence.lines.each do |line|
pry_instance.eval line, generated: true
end
end
# Checks +replay_sequence+ for the presence of neighboring replay calls.
# @example
# [1] pry(main)> hist --show 46894
# 46894: hist --replay 46675..46677
# [2] pry(main)> hist --show 46675..46677
# 46675: 1+1
# 46676: a = 100
# 46677: hist --tail
# [3] pry(main)> hist --replay 46894
# Error: Replay index 46894 points out to another replay call:
# `hist -r 46675..46677`
# [4] pry(main)>
#
# @raise [Pry::CommandError] If +replay_sequence+ contains another
# "hist --replay" call
# @param [String] replay_sequence The sequence of commands to be replayed
# (per saltum)
# @return [Boolean] `false` if +replay_sequence+ does not contain another
# "hist --replay" call
def check_for_juxtaposed_replay(replay_sequence)
if replay_sequence =~ /\Ahist(?:ory)?\b/
# Create *fresh* instance of Options for parsing of "hist" command.
slop_instance = slop
slop_instance.parse(replay_sequence.split(' ')[1..-1])
if slop_instance.present?(:r)
replay_sequence = replay_sequence.split("\n").join('; ')
index = opts[:r]
index = index.min if index.min == index.max || index.max.nil?
raise CommandError,
"Replay index #{index} points out to another replay call: " \
"`#{replay_sequence}`"
end
else
false
end
end
# Finds history depending on the given switch.
#
# @return [Pry::Code] if it finds `--all` (or `-a`) switch, returns all
# entries in history. Without the switch returns only the entries from the
# current Pry session.
def find_history
h = if opts.present?(:all)
Pry.history.to_a
else
Pry.history.to_a.last(Pry.history.session_line_count)
end
Pry::Code(Pry.history.filter(h[0..-2]))
end
end
Pry::Commands.add_command(Pry::Command::Hist)
Pry::Commands.alias_command 'history', 'hist'
end
end
@@ -0,0 +1,27 @@
# frozen_string_literal: true
class Pry
class Command
class ImportSet < Pry::ClassCommand
match 'import-set'
group 'Commands'
# TODO: Provide a better description with examples and a general conception
# of this command.
description 'Import a Pry command set.'
banner <<-'BANNER'
Import a Pry command set.
BANNER
# TODO: resolve unused parameter.
def process(_command_set_name)
raise CommandError, "Provide a command set name" if command_set.nil?
set = target.eval(arg_string)
pry_instance.commands.import set
end
end
Pry::Commands.add_command(Pry::Command::ImportSet)
end
end
@@ -0,0 +1,33 @@
# frozen_string_literal: true
class Pry
class Command
class JumpTo < Pry::ClassCommand
match 'jump-to'
group 'Navigating Pry'
description 'Jump to a binding further up the stack.'
banner <<-'BANNER'
Jump to a binding further up the stack, popping all bindings below.
BANNER
def process(break_level)
break_level = break_level.to_i
nesting_level = pry_instance.binding_stack.size - 1
max_nest_level = nesting_level - 1
case break_level
when nesting_level
output.puts "Already at nesting level #{nesting_level}"
when 0..max_nest_level
pry_instance.binding_stack = pry_instance.binding_stack[0..break_level]
else
output.puts "Invalid nest level. Must be between 0 and " \
"#{max_nest_level}. Got #{break_level}."
end
end
end
Pry::Commands.add_command(Pry::Command::JumpTo)
end
end
@@ -0,0 +1,42 @@
# frozen_string_literal: true
class Pry
class Command
class ListInspectors < Pry::ClassCommand
match 'list-inspectors'
group 'Input and Output'
description 'List the inspector procs available for use.'
banner <<-BANNER
Usage: list-inspectors
List the inspector procs available to print return values. You can use
change-inspector to switch between them.
BANNER
def process
output.puts heading("Available inspectors") + "\n"
inspector_map.each do |name, inspector|
output.write "Name: #{bold(name)}"
output.puts selected_inspector?(inspector) ? selected_text : ""
output.puts inspector[:description]
output.puts
end
end
private
def inspector_map
Pry::Inspector::MAP
end
def selected_text
red " (selected) "
end
def selected_inspector?(inspector)
pry_instance.print == inspector[:value]
end
Pry::Commands.add_command(self)
end
end
end
+114
View File
@@ -0,0 +1,114 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
match 'ls'
group 'Context'
description 'Show the list of vars and methods in the current scope.'
command_options shellwords: false, interpolate: false
banner <<-'BANNER'
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.
Also check out `find-method` command (run `help find-method`).
BANNER
def options(opt)
opt.on :m, :methods, "Show public methods defined on the Object"
opt.on :M, "instance-methods", "Show public 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 hash of local vars, sorted by descending size"
opt.on :c, :constants, "Show constants, highlighting classes (in blue), " \
"and exceptions (in purple).\n" \
"#{' ' * 32}Constants that are pending autoload? " \
"are also shown (in yellow)"
opt.on :i, :ivars, "Show instance variables (in blue) and class " \
"variables (in bright blue)"
opt.on :G, :grep, "Filter output by regular expression", argument: true
if Object.respond_to?(:deprecate_constant)
opt.on :d, :dconstants, "Show deprecated constants"
end
return unless Helpers::Platform.jruby?
opt.on :J, "all-java", "Show all the aliases for methods from java " \
"(default is to show only prettiest)"
end
# Exclude -q, -v and --grep because they,
# don't specify what the user wants to see.
def no_user_opts?
!(opts[:methods] || opts['instance-methods'] || opts[:ppp] ||
opts[:globals] || opts[:locals] || opts[:constants] || opts[:ivars])
end
def process
@interrogatee = args.empty? ? target_self : target.eval(args.join(' '))
raise_errors_if_arguments_are_weird
ls_entity = LsEntity.new(
interrogatee: @interrogatee,
no_user_opts: no_user_opts?,
opts: opts,
args: args,
pry_instance: pry_instance
)
pry_instance.pager.page ls_entity.entities_table
end
private
def error_list
any_args = args.any?
# rubocop:disable Style/CaseEquality
non_mod_interrogatee = !(Module === @interrogatee)
# rubocop:enable Style/CaseEquality
[
['-l does not make sense with a specified Object', :locals, any_args],
['-g does not make sense with a specified Object', :globals, any_args],
['-q does not make sense with -v', :quiet, opts.present?(:verbose)],
[
'-M only makes sense with a Module or a Class', 'instance-methods',
non_mod_interrogatee
],
[
'-c only makes sense with a Module or a Class', :constants,
any_args && non_mod_interrogatee
]
]
end
def raise_errors_if_arguments_are_weird
error_list.each do |message, option, invalid_expr|
raise Pry::CommandError, message if opts.present?(option) && invalid_expr
end
end
end
Pry::Commands.add_command(Pry::Command::Ls)
end
end
@@ -0,0 +1,54 @@
# frozen_string_literal: true
require 'forwardable'
class Pry
class Command
class Ls < Pry::ClassCommand
class Config
extend Forwardable
DEFAULT_OPTIONS = {
heading_color: :bright_blue,
public_method_color: :default,
private_method_color: :blue,
protected_method_color: :blue,
method_missing_color: :bright_red,
local_var_color: :yellow,
pry_var_color: :default, # e.g. _, pry_instance, _file_
instance_var_color: :blue, # e.g. @foo
class_var_color: :bright_blue, # e.g. @@foo
global_var_color: :default, # e.g. $CODERAY_DEBUG, $eventmachine_library
builtin_global_color: :cyan, # e.g. $stdin, $-w, $PID
pseudo_global_color: :cyan, # e.g. $~, $1..$9, $LAST_MATCH_INFO
constant_color: :default, # e.g. VERSION, ARGF
class_constant_color: :blue, # e.g. Object, Kernel
exception_constant_color: :magenta, # e.g. Exception, RuntimeError
unloaded_constant_color: :yellow, # Constant still in .autoload? state
separator: " ",
ceiling: [Object, Module, Class]
}.freeze
DEFAULT_OPTIONS.each_key do |key|
define_method key do
@config[key]
end
define_method "#{key}=" do |value|
@config[key] = value
end
end
def_delegators :@config, :[], :[]=, :each, :each_pair, :values, :keys, :to_a
def initialize(config)
@config = config
end
def self.default
new(DEFAULT_OPTIONS.dup)
end
end
end
end
end
@@ -0,0 +1,75 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
class Constants < Pry::Command::Ls::Formatter
DEPRECATED_CONSTANTS = %i[
Data Fixnum Bignum TimeoutError NIL FALSE TRUE
].tap do |constants|
constants << :JavaPackageModuleTemplate if Helpers::Platform.jruby?
end
include Pry::Command::Ls::Interrogatable
def initialize(interrogatee, no_user_opts, opts, pry_instance)
super(pry_instance)
@interrogatee = interrogatee
@no_user_opts = no_user_opts
@default_switch = opts[:constants]
@verbose_switch = opts[:verbose]
@dconstants = opts.dconstants?
end
def correct_opts?
super || (@no_user_opts && interrogating_a_module?)
end
def output_self
mod = interrogatee_mod
constants = WrappedModule.new(mod).constants(@verbose_switch)
output_section('constants', grep.regexp[format(mod, constants)])
end
private
def show_deprecated_constants?
@dconstants == true
end
def format(mod, constants)
constants.sort_by(&:downcase).map do |name|
if Object.respond_to?(:deprecate_constant) &&
DEPRECATED_CONSTANTS.include?(name) &&
!show_deprecated_constants?
next
end
if (const = (begin
!mod.autoload?(name) && (mod.const_get(name) || true)
rescue StandardError
nil
end))
if begin
const < Exception
rescue StandardError
false
end
color(:exception_constant, name)
elsif begin
mod.const_get(name).is_a?(Module)
rescue StandardError
false
end
color(:class_constant, name)
else
color(:constant, name)
end
else
color(:unloaded_constant, name)
end
end
end
end
end
end
end
@@ -0,0 +1,55 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
class Formatter
attr_writer :grep
attr_reader :pry_instance
def initialize(pry_instance)
@pry_instance = pry_instance
@target = pry_instance.current_context
@default_switch = nil
end
def write_out
return false unless correct_opts?
output_self
end
private
def color(type, str)
Pry::Helpers::Text.send pry_instance.config.ls.send("#{type}_color"), str
end
# Add a new section to the output.
# Outputs nothing if the section would be empty.
def output_section(heading, body)
return '' if body.compact.empty?
fancy_heading = Pry::Helpers::Text.bold(color(:heading, heading))
Pry::Helpers.tablify_or_one_line(fancy_heading, body, @pry_instance)
end
def format_value(value)
Pry::ColorPrinter.pp(value, ''.dup)
end
def correct_opts?
@default_switch
end
def output_self
raise NotImplementedError
end
def grep
@grep || proc { |x| x }
end
end
end
end
end
@@ -0,0 +1,50 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
class Globals < Pry::Command::Ls::Formatter
# Taken from "puts global_variables.inspect".
BUILTIN_GLOBALS =
%w[$" $$ $* $, $-0 $-F $-I $-K $-W $-a $-d $-i $-l $-p $-v $-w $. $/ $\\
$: $; $< $= $> $0 $ARGV $CONSOLE $DEBUG $DEFAULT_INPUT $DEFAULT_OUTPUT
$FIELD_SEPARATOR $FILENAME $FS $IGNORECASE $INPUT_LINE_NUMBER
$INPUT_RECORD_SEPARATOR $KCODE $LOADED_FEATURES $LOAD_PATH $NR $OFS
$ORS $OUTPUT_FIELD_SEPARATOR $OUTPUT_RECORD_SEPARATOR $PID $PROCESS_ID
$PROGRAM_NAME $RS $VERBOSE $deferr $defout $stderr $stdin $stdout].freeze
# `$SAFE` and `$?` are thread-local, the exception stuff only works in a
# rescue clause, everything else is basically a local variable with a `$`
# in its name.
PSEUDO_GLOBALS =
%w[$! $' $& $` $@ $? $+ $_ $~ $1 $2 $3 $4 $5 $6 $7 $8 $9
$CHILD_STATUS $SAFE $ERROR_INFO $ERROR_POSITION $LAST_MATCH_INFO
$LAST_PAREN_MATCH $LAST_READ_LINE $MATCH $POSTMATCH $PREMATCH].freeze
def initialize(opts, pry_instance)
super(pry_instance)
@default_switch = opts[:globals]
end
def output_self
variables = format(@target.eval('global_variables'))
output_section('global variables', grep.regexp[variables])
end
private
def format(globals)
globals.map(&:to_s).sort_by(&:downcase).map do |name|
if PSEUDO_GLOBALS.include?(name)
color(:pseudo_global, name)
elsif BUILTIN_GLOBALS.include?(name)
color(:builtin_global, name)
else
color(:global_var, name)
end
end
end
end
end
end
end
@@ -0,0 +1,23 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
class Grep
def initialize(grep_regexp)
@grep_regexp = grep_regexp
end
def regexp
proc { |x|
if x.instance_of?(Array)
x.grep(@grep_regexp)
else
x =~ @grep_regexp
end
}
end
end
end
end
end
@@ -0,0 +1,40 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
class InstanceVars < Pry::Command::Ls::Formatter
include Pry::Command::Ls::Interrogatable
def initialize(interrogatee, no_user_opts, opts, pry_instance)
super(pry_instance)
@interrogatee = interrogatee
@no_user_opts = no_user_opts
@default_switch = opts[:ivars]
end
def correct_opts?
super || @no_user_opts
end
def output_self
ivars = if Object === @interrogatee # rubocop:disable Style/CaseEquality
Pry::Method.safe_send(@interrogatee, :instance_variables)
else
[] # TODO: BasicObject support
end
kvars = Pry::Method.safe_send(interrogatee_mod, :class_variables)
ivars_out = output_section('instance variables', format(:instance_var, ivars))
kvars_out = output_section('class variables', format(:class_var, kvars))
ivars_out + kvars_out
end
private
def format(type, vars)
vars.sort_by { |var| var.to_s.downcase }.map { |var| color(type, var) }
end
end
end
end
end
@@ -0,0 +1,24 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
module Interrogatable
private
def interrogating_a_module?
Module === @interrogatee # rubocop:disable Style/CaseEquality
end
def interrogatee_mod
if interrogating_a_module?
@interrogatee
else
singleton = Pry::Method.singleton_class_of(@interrogatee)
singleton.ancestors.grep(::Class).reject { |c| c == singleton }.first
end
end
end
end
end
end
@@ -0,0 +1,55 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
module JRubyHacks
private
# JRuby creates lots of aliases for methods imported from java in an attempt
# to make life easier for ruby programmers. (e.g. getFooBar becomes
# get_foo_bar and foo_bar, and maybe foo_bar? if it returns a Boolean). The
# full transformations are in the assignAliases method of:
# https://github.com/jruby/jruby/blob/master/src/org/jruby/javasupport/JavaClass.java
#
# This has the unfortunate side-effect of making the output of ls even more
# incredibly verbose than it normally would be for these objects; and so we
# filter out all but the nicest of these aliases here.
#
# TODO: This is a little bit vague, better heuristics could be used.
# JRuby also has a lot of scala-specific logic, which we don't copy.
def trim_jruby_aliases(methods)
grouped = methods.group_by do |m|
m.name.sub(/\A(is|get|set)(?=[A-Z_])/, '').gsub(/[_?=]/, '').downcase
end
grouped.flat_map do |_key, values|
values = values.sort_by do |m|
rubbishness(m.name)
end
found = []
values.select do |x|
(found.none? { |y| x == y }) && found << x
end
end
end
# When removing jruby aliases, we want to keep the alias that is
# "least rubbish" according to this metric.
def rubbishness(name)
name.each_char.map do |x|
case x
when /[A-Z]/
1
when '?', '=', '!'
-2
else
0
end
end.inject(&:+) + (name.size / 100.0)
end
end
end
end
end
@@ -0,0 +1,37 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
class LocalNames < Pry::Command::Ls::Formatter
def initialize(no_user_opts, args, pry_instance)
super(pry_instance)
@no_user_opts = no_user_opts
@args = args
@sticky_locals = pry_instance.sticky_locals
end
def correct_opts?
super || (@no_user_opts && @args.empty?)
end
def output_self
local_vars = grep.regexp[@target.eval('local_variables')]
output_section('locals', format(local_vars))
end
private
def format(locals)
locals.sort_by(&:downcase).map do |name|
if @sticky_locals.include?(name.to_sym)
color(:pry_var, name)
else
color(:local_var, name)
end
end
end
end
end
end
end
@@ -0,0 +1,47 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
class LocalVars < Pry::Command::Ls::Formatter
def initialize(opts, pry_instance)
super(pry_instance)
@default_switch = opts[:locals]
@sticky_locals = pry_instance.sticky_locals
end
def output_self
locals = @target.eval('local_variables').reject do |e|
@sticky_locals.key?(e.to_sym)
end
name_value_pairs = locals.map do |name|
[name, @target.eval(name.to_s)]
end
format(name_value_pairs).join('')
end
private
def format(name_value_pairs)
sorted = name_value_pairs.sort_by do |_name, value|
value.to_s.size
end
sorted.reverse.map do |name, value|
colorized_assignment_style(name, format_value(value))
end
end
def colorized_assignment_style(lhs, rhs, desired_width = 7)
colorized_lhs = color(:local_var, lhs)
color_escape_padding = colorized_lhs.size - lhs.size
pad = desired_width + color_escape_padding
Kernel.format(
"%-#{pad}<name>s = %<value>s",
name: color(:local_var, colorized_lhs),
value: rhs
)
end
end
end
end
end
@@ -0,0 +1,65 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
class LsEntity
attr_reader :pry_instance
def initialize(opts)
@interrogatee = opts[:interrogatee]
@no_user_opts = opts[:no_user_opts]
@opts = opts[:opts]
@args = opts[:args]
@grep = Grep.new(Regexp.new(opts[:opts][:G] || '.'))
@pry_instance = opts.delete(:pry_instance)
end
def entities_table
entities.map(&:write_out).select { |o| o }.join('')
end
private
def grep(entity)
entity.tap { |o| o.grep = @grep }
end
def globals
grep Globals.new(@opts, pry_instance)
end
def constants
grep Constants.new(@interrogatee, @no_user_opts, @opts, pry_instance)
end
def methods
grep(Methods.new(@interrogatee, @no_user_opts, @opts, pry_instance))
end
def self_methods
grep SelfMethods.new(@interrogatee, @no_user_opts, @opts, pry_instance)
end
def instance_vars
grep InstanceVars.new(@interrogatee, @no_user_opts, @opts, pry_instance)
end
def local_names
grep LocalNames.new(@no_user_opts, @args, pry_instance)
end
def local_vars
LocalVars.new(@opts, pry_instance)
end
def entities
[
globals, constants, methods, self_methods, instance_vars, local_names,
local_vars
]
end
end
end
end
end
@@ -0,0 +1,55 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
class Methods < Pry::Command::Ls::Formatter
include Pry::Command::Ls::Interrogatable
include Pry::Command::Ls::MethodsHelper
def initialize(interrogatee, no_user_opts, opts, pry_instance)
super(pry_instance)
@interrogatee = interrogatee
@no_user_opts = no_user_opts
@default_switch = opts[:methods]
@instance_methods_switch = opts['instance-methods']
@ppp_switch = opts[:ppp]
@jruby_switch = opts['all-java']
@quiet_switch = opts[:quiet]
@verbose_switch = opts[:verbose]
end
def output_self
methods = all_methods.group_by(&:owner)
# Reverse the resolution order so that the most useful information
# appears right by the prompt.
resolution_order.take_while(&below_ceiling).reverse.map do |klass|
methods_here = (methods[klass] || []).select { |m| grep.regexp[m.name] }
heading = "#{Pry::WrappedModule.new(klass).method_prefix}methods"
output_section(heading, format(methods_here))
end.join('')
end
private
def correct_opts?
super || @instance_methods_switch || @ppp_switch || @no_user_opts
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
ceiling = if @quiet_switch
[Pry::Method.safe_send(interrogatee_mod, :ancestors)[1]] +
pry_instance.config.ls.ceiling
elsif @verbose_switch
[]
else
pry_instance.config.ls.ceiling.dup
end
->(klass) { !ceiling.include?(klass) }
end
end
end
end
end
@@ -0,0 +1,50 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
module MethodsHelper
include Pry::Command::Ls::JRubyHacks
private
# Get all the methods that we'll want to output.
def all_methods(instance_methods = false)
methods = if instance_methods || @instance_methods_switch
Pry::Method.all_from_class(@interrogatee)
else
Pry::Method.all_from_obj(@interrogatee)
end
if Pry::Helpers::Platform.jruby? && !@jruby_switch
methods = trim_jruby_aliases(methods)
end
methods.select { |method| @ppp_switch || method.visibility == :public }
end
def resolution_order
if @instance_methods_switch
Pry::Method.instance_resolution_order(@interrogatee)
else
Pry::Method.resolution_order(@interrogatee)
end
end
def format(methods)
methods.sort_by(&:name).map do |method|
if method.name == 'method_missing'
color(:method_missing, 'method_missing')
elsif method.visibility == :private
color(:private_method, method.name)
elsif method.visibility == :protected
color(:protected_method, method.name)
else
color(:public_method, method.name)
end
end
end
end
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
class Pry
class Command
class Ls < Pry::ClassCommand
class SelfMethods < Pry::Command::Ls::Formatter
include Pry::Command::Ls::Interrogatable
include Pry::Command::Ls::MethodsHelper
def initialize(interrogatee, no_user_opts, opts, pry_instance)
super(pry_instance)
@interrogatee = interrogatee
@no_user_opts = no_user_opts
@ppp_switch = opts[:ppp]
@jruby_switch = opts['all-java']
end
def output_self
methods = all_methods(true).select do |m|
m.owner == @interrogatee && grep.regexp[m.name]
end
heading = "#{Pry::WrappedModule.new(@interrogatee).method_prefix}methods"
output_section(heading, format(methods))
end
private
def correct_opts?
@no_user_opts && interrogating_a_module?
end
end
end
end
end
@@ -0,0 +1,29 @@
# frozen_string_literal: true
class Pry
class Command
class Nesting < Pry::ClassCommand
match 'nesting'
group 'Navigating Pry'
description 'Show nesting information.'
banner <<-'BANNER'
Show nesting information.
BANNER
def process
output.puts 'Nesting status:'
output.puts '--'
pry_instance.binding_stack.each_with_index do |obj, level|
if level == 0
output.puts "#{level}. #{Pry.view_clip(obj.eval('self'))} (Pry top level)"
else
output.puts "#{level}. #{Pry.view_clip(obj.eval('self'))}"
end
end
end
end
Pry::Commands.add_command(Pry::Command::Nesting)
end
end
@@ -0,0 +1,113 @@
# frozen_string_literal: true
class Pry
class Command
class Play < Pry::ClassCommand
match 'play'
group 'Editing'
description 'Playback a string variable, method, line, or file as input.'
banner <<-'BANNER'
Usage: play [OPTIONS] [--help]
The play command enables you to replay code from files and methods as if they
were entered directly in the Pry REPL.
play --lines 149..153 # assumes current context
play -i 20 --lines 1..3 # assumes lines of the input expression at 20
play -o 4 # the output of an expression at 4
play Pry#repl -l 1..-1 # play the contents of Pry#repl method
play -e 2 # play from specified line until end of valid expression
play hello.rb # play a file
play Rakefile -l 5 # play line 5 of a file
play -d hi # play documentation of hi method
play hi --open # play hi method and leave it open
https://github.com/pry/pry/wiki/User-Input#wiki-Play
BANNER
def options(opt)
CodeCollector.inject_options(opt)
opt.on :open, 'Plays the selected content except the last line. Useful' \
' for replaying methods and leaving the method definition' \
' "open". `amend-line` can then be used to' \
' modify the method.'
opt.on :e, :expression=, 'Executes until end of valid expression', as: Integer
opt.on :p, :print, 'Prints executed code'
end
def process
@cc = CodeCollector.new(args, opts, pry_instance)
perform_play
show_input
end
def perform_play
eval_string << content_after_options
run "fix-indent"
end
def show_input
return unless opts.present?(:print)
return unless Pry::Code.complete_expression?(eval_string)
run 'show-input'
end
def content_after_options
if opts.present?(:open)
restrict_to_lines(content, (0..-2))
elsif opts.present?(:expression)
content_at_expression
else
content
end
end
def content_at_expression
code_object.expression_at(opts[:expression])
end
def code_object
Pry::Code.new(content)
end
def should_use_default_file?
!args.first && !opts.present?(:in) && !opts.present?(:out)
end
def content
if should_use_default_file?
file_content
else
@cc.content
end
end
# The file to play from when no code object is specified.
# e.g `play --lines 4..10`
def default_file
file =
if target.respond_to?(:source_location)
target.source_location.first
else
target.eval("__FILE__")
end
file && File.expand_path(file)
end
def file_content
if !default_file || !File.exist?(default_file)
raise CommandError, "File does not exist! File was: #{default_file.inspect}"
end
@cc.restrict_to_lines(File.read(default_file), @cc.line_range)
end
end
Pry::Commands.add_command(Pry::Command::Play)
end
end
@@ -0,0 +1,30 @@
# frozen_string_literal: true
class Pry
class Command
class PryBacktrace < Pry::ClassCommand
match 'pry-backtrace'
group 'Context'
description 'Show the backtrace for the Pry session.'
banner <<-BANNER
Usage: pry-backtrace [OPTIONS] [--help]
Show the backtrace for the position in the code where Pry was started. This can
be used to infer the behavior of the program immediately before it entered Pry,
just like the backtrace property of an exception.
NOTE: if you are looking for the backtrace of the most recent exception raised,
just type: `_ex_.backtrace` instead.
See: https://github.com/pry/pry/wiki/Special-Locals
BANNER
def process
text = "#{bold('Backtrace:')}\n--\n#{pry_instance.backtrace.join("\n")}"
pry_instance.pager.page(text)
end
end
Pry::Commands.add_command(Pry::Command::PryBacktrace)
end
end
@@ -0,0 +1,21 @@
# frozen_string_literal: true
class Pry
class Command
class Version < Pry::ClassCommand
match 'pry-version'
group 'Misc'
description 'Show Pry version.'
banner <<-'BANNER'
Show Pry version.
BANNER
def process
output.puts "Pry version: #{Pry::VERSION} on Ruby #{RUBY_VERSION}."
end
end
Pry::Commands.add_command(Pry::Command::Version)
end
end
@@ -0,0 +1,38 @@
# frozen_string_literal: true
class Pry
# N.B. using a regular expression here so that "raise-up 'foo'" does the right thing.
class Command
class RaiseUp < Pry::ClassCommand
match(/raise-up(!?\b.*)/)
group 'Context'
description 'Raise an exception out of the current pry instance.'
command_options listing: 'raise-up'
banner <<-BANNER
Raise up, like exit, allows you to quit pry. Instead of returning a value
however, it raises an exception. If you don't provide the exception to be
raised, it will use the most recent exception (in pry `_ex_`).
When called as raise-up! (with an exclamation mark), this command raises the
exception through any nested prys you have created by "cd"ing into objects.
raise-up "get-me-out-of-here"
# This is equivalent to the command above.
raise "get-me-out-of-here"
raise-up
BANNER
def process
return _pry.pager.page help if captures[0] =~ /(-h|--help)\b/
# Handle 'raise-up', 'raise-up "foo"', 'raise-up RuntimeError, 'farble'
# in a rubyesque manner
target.eval("pry_instance.raise_up#{captures[0]}")
end
end
Pry::Commands.add_command(Pry::Command::RaiseUp)
end
end
@@ -0,0 +1,74 @@
# frozen_string_literal: true
class Pry
class Command
class ReloadCode < Pry::ClassCommand
match 'reload-code'
group 'Misc'
description 'Reload the source file that contains the specified code object.'
banner <<-'BANNER'
Reload the source file that contains the specified code object.
e.g reload-code MyClass#my_method #=> reload a method
reload-code MyClass #=> reload a class
reload-code my-command #=> reload a pry command
reload-code self #=> reload the current object
reload-code #=> reload the current file or object
BANNER
def process
if !args.empty?
reload_object(args.join(" "))
elsif internal_binding?(target)
reload_object("self")
else
reload_current_file
end
end
private
def current_file
file =
if target.respond_to?(:source_location)
target.source_location.first
else
target.eval("__FILE__")
end
File.expand_path file
end
def reload_current_file
unless File.exist?(current_file)
raise CommandError, "Current file: #{current_file} cannot be found on disk!"
end
load current_file
output.puts "The current file: #{current_file} was reloaded!"
end
def reload_object(identifier)
code_object = Pry::CodeObject.lookup(identifier, pry_instance)
check_for_reloadability(code_object, identifier)
load code_object.source_file
output.puts "#{identifier} was reloaded!"
end
def check_for_reloadability(code_object, identifier)
if !code_object || !code_object.source_file
raise CommandError, "Cannot locate #{identifier}!"
end
return if File.exist?(code_object.source_file)
raise CommandError,
"Cannot reload #{identifier} as it has no associated file on disk. " \
"File found was: #{code_object.source_file}"
end
end
Pry::Commands.add_command(Pry::Command::ReloadCode)
Pry::Commands.alias_command 'reload-method', 'reload-code'
end
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
class Pry
class Command
class Reset < Pry::ClassCommand
match 'reset'
group 'Context'
description 'Reset the REPL to a clean state.'
banner <<-'BANNER'
Reset the REPL to a clean state.
BANNER
def process
output.puts 'Pry reset.'
exec 'pry'
end
end
Pry::Commands.add_command(Pry::Command::Reset)
end
end
@@ -0,0 +1,75 @@
# frozen_string_literal: true
require 'stringio'
class Pry
class Command
class Ri < Pry::ClassCommand
match 'ri'
group 'Introspection'
description 'View ri documentation.'
banner <<-'BANNER'
Usage: ri [spec]
View ri documentation. Relies on the "rdoc" gem being installed.
See also "show-doc" command.
ri Array#each
BANNER
def process(spec)
unless spec
return output.puts(
"Please provide a class, module, or method name (e.g: ri Array#push)"
)
end
# Lazily load RI
require 'rdoc/ri/driver'
unless defined? RDoc::RI::PryDriver
# Subclass RI so that it formats its output nicely, and uses `lesspipe`.
subclass = Class.new(RDoc::RI::Driver) # the hard way.
subclass.class_eval do
def initialize(pager, opts)
@pager = pager
super opts
end
def page
paging_text = StringIO.new
yield paging_text
@pager.page(paging_text.string)
end
def formatter(_io)
if @formatter_klass
@formatter_klass.new
else
RDoc::Markup::ToAnsi.new
end
end
end
RDoc::RI.const_set :PryDriver, subclass # hook it up!
end
# Spin-up an RI instance.
ri = RDoc::RI::PryDriver.new(
pry_instance.pager, use_stdout: true, interactive: false
)
begin
ri.display_names [spec] # Get the documentation (finally!)
rescue RDoc::RI::Driver::NotFoundError => e
output.puts "error: '#{e.name}' not found"
end
end
end
Pry::Commands.add_command(Pry::Command::Ri)
end
end
@@ -0,0 +1,63 @@
# frozen_string_literal: true
class Pry
class Command
class SaveFile < Pry::ClassCommand
match 'save-file'
group 'Input and Output'
description 'Export to a file using content from the REPL.'
banner <<-'BANNER'
Usage: save-file [OPTIONS] --to [FILE]
Export to a file using content from the REPL.
save-file my_method --to hello.rb
save-file -i 1..10 --to hello.rb --append
save-file show-method --to my_command.rb
save-file sample_file.rb --lines 2..10 --to output_file.rb
BANNER
def options(opt)
CodeCollector.inject_options(opt)
opt.on :to=, "Specify the output file path"
opt.on :a, :append, "Append output to file"
end
def process
@cc = CodeCollector.new(args, opts, pry_instance)
raise CommandError, "Found no code to save." if @cc.content.empty?
if !file_name
display_content
else
save_file
end
end
def file_name
opts[:to] || nil
end
def save_file
File.open(file_name, mode) do |f|
f.puts @cc.content
end
output.puts "#{file_name} successfully saved"
end
def display_content
output.puts @cc.content
output.puts "\n\n--\nPlease use `--to FILE` to export to a file."
output.puts "No file saved!\n--"
end
def mode
opts.present?(:append) ? "a" : "w"
end
end
Pry::Commands.add_command(Pry::Command::SaveFile)
end
end
@@ -0,0 +1,75 @@
# frozen_string_literal: true
class Pry
class Command
class ShellCommand < Pry::ClassCommand
match(/\.(.*)/)
group 'Input and Output'
description "All text following a '.' is forwarded to the shell."
command_options listing: '.<shell command>', use_prefix: false,
takes_block: true, state: %i[old_pwd]
banner <<-'BANNER'
Usage: .COMMAND_NAME
All text following a "." is forwarded to the shell.
.ls -aF
.uname
BANNER
def process(cmd)
if cmd =~ /^cd\s*(.*)/i
process_cd parse_destination(Regexp.last_match(1))
else
pass_block(cmd)
if command_block
command_block.call `#{cmd}`
else
pry_instance.config.system.call(output, cmd, pry_instance)
end
end
end
private
def parse_destination(dest)
return "~" if dest.empty?
return dest unless dest == "-"
state.old_pwd || raise(CommandError, "No prior directory available")
end
def process_cd(dest)
state.old_pwd = Dir.pwd
Dir.chdir(File.expand_path(path_from_cd_path(dest) || dest))
rescue Errno::ENOENT
raise CommandError, "No such directory: #{dest}"
end
def cd_path_env
Pry::Env['CDPATH']
end
def cd_path_exists?
cd_path_env && cd_path_env.length.nonzero?
end
def path_from_cd_path(dest)
return if !(dest && cd_path_exists?) || special_case_path?(dest)
cd_path_env.split(File::PATH_SEPARATOR).each do |path|
return path if File.directory?(path) && path.split(File::SEPARATOR).last == dest
end
nil
end
def special_case_path?(dest)
['.', '..', '-'].include?(dest) || dest =~ /\A[#{File::PATH_SEPARATOR}~]/
end
end
Pry::Commands.add_command(Pry::Command::ShellCommand)
end
end
@@ -0,0 +1,30 @@
# frozen_string_literal: true
class Pry
class Command
class ShellMode < Pry::ClassCommand
match 'shell-mode'
group 'Input and Output'
description 'Toggle shell mode. Bring in pwd prompt and file completion.'
command_options state: %i[disabled prev_prompt]
banner <<-'BANNER'
Toggle shell mode. Bring in pwd prompt and file completion.
BANNER
def process
state.disabled ^= true
if state.disabled
state.prev_prompt = pry_instance.prompt
pry_instance.prompt = Pry::Prompt[:shell]
else
pry_instance.prompt = state.prev_prompt
end
end
end
Pry::Commands.add_command(Pry::Command::ShellMode)
Pry::Commands.alias_command 'file-mode', 'shell-mode'
end
end
@@ -0,0 +1,93 @@
# frozen_string_literal: true
class Pry
class Command
class ShowDoc < Command::ShowInfo
include Pry::Helpers::DocumentationHelpers
match 'show-doc'
group 'Introspection'
description 'Show the documentation for a method or class.'
banner <<-BANNER
Usage: show-doc [OPTIONS] [METH]
Aliases: ?
Show the documentation for a method or class. Tries instance methods first and
then methods by default.
show-doc hi_method # docs for hi_method
show-doc Pry # for Pry class
show-doc Pry -a # for all definitions of Pry class (all monkey patches)
BANNER
def process
super
output.puts(
"\nWARNING: the show-doc command is deprecated. It will be removed " \
"from future Pry versions.\nPlease use 'show-source' with the -d " \
"(or --doc) switch instead\nExample: show-source #{obj_name} -d"
)
end
# The docs for code_object prepared for display.
def content_for(code_object)
Code.new(
render_doc_markup_for(code_object),
start_line_for(code_object),
:text
).with_line_numbers(use_line_numbers?).to_s
end
# process the markup (if necessary) and apply colors
def render_doc_markup_for(code_object)
docs = docs_for(code_object)
if code_object.command?
# command '--help' shouldn't use markup highlighting
docs
else
if docs.empty?
raise CommandError, "No docs found for: #{obj_name || 'current context'}"
end
process_comment_markup(docs)
end
end
# Return docs for the code_object, adjusting for whether the code_object
# has yard docs available, in which case it returns those.
# (note we only have to check yard docs for modules since they can
# have multiple docs, but methods can only be doc'd once so we
# dont need to check them)
def docs_for(code_object)
if code_object.module_with_yard_docs?
# yard docs
code_object.yard_doc
else
# normal docs (i.e comments above method/module/command)
code_object.doc
end
end
# Which sections to include in the 'header', can toggle: :owner,
# :signature and visibility.
def header_options
super.merge signature: true
end
# figure out start line of docs by back-calculating based on
# number of lines in the comment and the start line of the code_object
# @return [Fixnum] start line of docs
def start_line_for(code_object)
return 1 if code_object.command? || opts.present?(:'base-one')
return 1 unless code_object.source_line
code_object.source_line - code_object.doc.lines.count
end
end
Pry::Commands.add_command(Pry::Command::ShowDoc)
end
end
@@ -0,0 +1,234 @@
# frozen_string_literal: true
class Pry
class Command
class ShowInfo < Pry::ClassCommand
extend Pry::Helpers::BaseHelpers
command_options shellwords: false, interpolate: false
def initialize(*)
super
@used_super = nil
end
def options(opt)
opt.on :s, :super, "Select the 'super' method. Can be repeated to " \
"traverse the ancestors", as: :count
opt.on :l, "line-numbers", "Show line numbers"
opt.on :b, "base-one", "Show line numbers but start numbering at 1 " \
"(useful for `amend-line` and `play` commands)"
opt.on :a, :all, "Show all definitions and monkeypatches of the " \
"module/class"
end
def process
code_object = Pry::CodeObject.lookup(obj_name, pry_instance, super: opts[:super])
raise CommandError, no_definition_message unless code_object
@original_code_object = code_object
if !obj_name && code_object.c_module? && !opts[:all]
result = "You're inside an object, whose class is defined by means of " \
"the C Ruby API.\nPry cannot display the information for this " \
"class."
if code_object.candidates.any?
result += "\nHowever, you can view monkey-patches applied to this " \
"class.\n.Just execute the same command with the '--all' " \
"switch."
end
elsif show_all_modules?(code_object)
# show all monkey patches for a module
result = content_and_headers_for_all_module_candidates(code_object)
else
# show a specific code object
co = code_object_with_accessible_source(code_object)
result = content_and_header_for_code_object(co)
end
set_file_and_dir_locals(code_object.source_file)
pry_instance.pager.page result
end
# This method checks whether the `code_object` is a WrappedModule, if it
# is, then it returns the first candidate (monkeypatch) with accessible
# source (or docs). If `code_object` is not a WrappedModule (i.e a method
# or a command) then the `code_object` itself is just returned.
#
# @return [Pry::WrappedModule, Pry::Method, Pry::Command]
def code_object_with_accessible_source(code_object)
return code_object unless code_object.is_a?(WrappedModule)
candidate = code_object.candidates.find(&:source)
return candidate if candidate
raise CommandError, no_definition_message unless valid_superclass?(code_object)
@used_super = true
code_object_with_accessible_source(code_object.super)
end
def valid_superclass?(code_object)
code_object.super && code_object.super.wrapped != Object
end
def content_and_header_for_code_object(code_object)
header(code_object) + content_for(code_object)
end
def content_and_headers_for_all_module_candidates(mod)
result = "Found #{mod.number_of_candidates} candidates for " \
"`#{mod.name}` definition:\n"
mod.number_of_candidates.times do |v|
candidate = mod.candidate(v)
begin
result += "\nCandidate #{v + 1}/#{mod.number_of_candidates}: " \
"#{candidate.source_file}:#{candidate.source_line}\n"
content = content_for(candidate)
result += "Number of lines: #{content.lines.count}\n\n" + content
rescue Pry::RescuableException
result += "\nNo content found.\n"
next
end
end
result
end
def no_definition_message
"Couldn't locate a definition for #{obj_name}"
end
# Generate a header (meta-data information) for all the code
# object types: methods, modules, commands, procs...
def header(code_object)
file_name, line_num = file_and_line_for(code_object)
content = content_for(code_object)
h = "\n#{bold('From:')} #{file_name}"
h += code_object_header(code_object, line_num)
h += "\n#{bold('Number of lines:')} " + "#{content.lines.count}\n\n"
if @used_super
h += bold('** Warning:')
h += " Cannot find code for #{@original_code_object.nonblank_name}. " \
"Showing superclass #{code_object.nonblank_name} instead. **\n\n"
end
if content.lines.none?
h += bold('** Warning:')
h += " Cannot find code for '#{code_object.name}' (source_location is nil)"
end
h
end
def code_object_header(code_object, line_num)
if code_object.real_method_object?
method_header(code_object, line_num)
# It sucks we have to test for both Pry::WrappedModule and
# WrappedModule::Candidate, probably indicates a deep refactor needs
# to happen in those classes.
elsif code_object.is_a?(Pry::WrappedModule) ||
code_object.is_a?(Pry::WrappedModule::Candidate)
module_header(code_object, line_num)
else
""
end
end
def method_header(code_object, line_num)
h = ""
h += (code_object.c_method? ? ' (C Method):' : ":#{line_num}:")
h += method_sections(code_object)[:owner]
h += method_sections(code_object)[:visibility]
h += method_sections(code_object)[:signature]
h
end
def module_header(code_object, line_num)
h = ""
h += ":#{line_num}\n"
h += bold(code_object.module? ? "Module" : "Class")
h += " #{bold('name:')} #{code_object.nonblank_name}"
if code_object.number_of_candidates > 1
h += bold("\nNumber of monkeypatches: ")
h += code_object.number_of_candidates.to_s
h += ". Use the `-a` option to display all available monkeypatches"
end
h
end
def method_sections(code_object)
{
owner: "\n#{bold('Owner:')} #{code_object.owner || 'N/A'}\n",
visibility: "#{bold('Visibility:')} #{code_object.visibility}",
signature: "\n#{bold('Signature:')} #{code_object.signature}"
}.merge(header_options) { |_key, old, new| (new && old).to_s }
end
def header_options
{
owner: true,
visibility: true,
signature: nil
}
end
def show_all_modules?(code_object)
code_object.is_a?(Pry::WrappedModule) && opts.present?(:all)
end
def obj_name
@obj_name ||= args.empty? ? nil : args.join(' ')
end
def use_line_numbers?
opts.present?(:b) || opts.present?(:l)
end
def start_line_for(code_object)
if opts.present?(:'base-one')
1
else
code_object.source_line || 1
end
end
# takes into account possible yard docs, and returns yard_file / yard_line
# Also adjusts for start line of comments (using start_line_for), which it
# has to infer by subtracting number of lines of comment from start line
# of code_object
def file_and_line_for(code_object)
if code_object.module_with_yard_docs?
[code_object.yard_file, code_object.yard_line]
else
[code_object.source_file, start_line_for(code_object)]
end
end
def complete(input)
if input =~ /([^ ]*)#([a-z0-9_]*)\z/
prefix = Regexp.last_match(1)
search = Regexp.last_match(2)
methods =
begin
# rubocop:disable Security/Eval
Pry::Method.all_from_class(binding.eval(prefix))
# rubocop:enable Security/Eval
rescue RescuableException
return super
end
methods.map do |method|
[prefix, method.name].join('#') if method.name.start_with?(search)
end.compact
else
super
end
end
end
end
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
class Pry
class Command
class ShowInput < Pry::ClassCommand
match 'show-input'
group 'Editing'
description 'Show the contents of the input buffer for the current ' \
'multi-line expression.'
banner <<-'BANNER'
Show the contents of the input buffer for the current multi-line expression.
BANNER
def process
output.puts Code.new(eval_string).with_line_numbers
end
end
Pry::Commands.add_command(Pry::Command::ShowInput)
end
end
@@ -0,0 +1,118 @@
# frozen_string_literal: true
class Pry
class Command
class ShowSource < Command::ShowInfo
include Pry::Helpers::DocumentationHelpers
match 'show-source'
group 'Introspection'
description 'Show the source for a method or class.'
banner <<-'BANNER'
Usage: show-source [OPTIONS] [METH|CLASS]
Aliases: $, show-method
Show the source for a method or class. Tries instance methods first and then
methods by default.
show-source hi_method
show-source hi_method
show-source Pry#rep # source for Pry#rep method
show-source Pry # for Pry class
show-source Pry -a # for all Pry class definitions (all monkey patches)
show-source Pry.foo -e # for class of the return value of expression `Pry.foo`
show-source Pry --super # for superclass of Pry (Object class)
show-source Pry -d # include documentation
https://github.com/pry/pry/wiki/Source-browsing#wiki-Show_method
BANNER
def options(opt)
opt.on :e, :eval, "evaluate the command's argument as a ruby " \
"expression and show the class its return value"
opt.on :d, :doc, 'include documentation in the output'
super(opt)
end
def process
if opts.present?(:e)
obj = target.eval(args.first)
self.args = Array.new(1) { obj.is_a?(Module) ? obj.name : obj.class.name }
end
super
end
# The source for code_object prepared for display.
def content_for(code_object)
content = ''
if opts.present?(:d)
code = Code.new(
render_doc_markup_for(code_object), start_line_for(code_object), :text
)
content += code.with_line_numbers(use_line_numbers?).to_s
content += "\n"
end
code = Code.new(
code_object.source || [], start_line_for(code_object)
)
content += code.with_line_numbers(use_line_numbers?).highlighted
content
end
# process the markup (if necessary) and apply colors
def render_doc_markup_for(code_object)
docs = docs_for(code_object)
if code_object.command?
# command '--help' shouldn't use markup highlighting
docs
else
if docs.empty?
raise CommandError, "No docs found for: #{obj_name || 'current context'}"
end
process_comment_markup(docs)
end
end
# Return docs for the code_object, adjusting for whether the code_object
# has yard docs available, in which case it returns those.
# (note we only have to check yard docs for modules since they can
# have multiple docs, but methods can only be doc'd once so we
# dont need to check them)
def docs_for(code_object)
if code_object.module_with_yard_docs?
# yard docs
code_object.yard_doc
else
# normal docs (i.e comments above method/module/command)
code_object.doc
end
end
# Which sections to include in the 'header', can toggle: :owner,
# :signature and visibility.
def header_options
super.merge signature: true
end
# figure out start line of docs by back-calculating based on
# number of lines in the comment and the start line of the code_object
# @return [Fixnum] start line of docs
def start_line_for(code_object)
return 1 if code_object.command? || opts.present?(:'base-one')
return 1 unless code_object.source_line
code_object.source_line - code_object.doc.lines.count
end
end
Pry::Commands.add_command(Pry::Command::ShowSource)
Pry::Commands.alias_command 'show-method', 'show-source'
Pry::Commands.alias_command '$', 'show-source'
Pry::Commands.alias_command '?', 'show-source -d'
end
end
@@ -0,0 +1,44 @@
# frozen_string_literal: true
class Pry
class Command
class Stat < Pry::ClassCommand
match 'stat'
group 'Introspection'
description 'View method information and set _file_ and _dir_ locals.'
command_options shellwords: false
banner <<-'BANNER'
Usage: stat [OPTIONS] [METH]
Show method information for method METH and set _file_ and _dir_ locals.
stat hello_method
BANNER
def options(opt)
method_options(opt)
end
def process
meth = method_object
aliases = meth.aliases
output.puts(unindent(<<-OUTPUT))
Method Information:
--
Name: #{meth.name}
Alias#{'es' if aliases.length > 1}: #{aliases.any? ? aliases.join(', ') : 'None.'}
Owner: #{meth.owner || 'Unknown'}
Visibility: #{meth.visibility}
Type: #{meth.is_a?(::Method) ? 'Bound' : 'Unbound'}
Arity: #{meth.arity}
Method Signature: #{meth.signature}
Source Location: #{meth.source_location ? meth.source_location.join(':') : 'Not found.'}
OUTPUT
end
end
Pry::Commands.add_command(Pry::Command::Stat)
end
end
@@ -0,0 +1,29 @@
# frozen_string_literal: true
class Pry
class Command
class SwitchTo < Pry::ClassCommand
match 'switch-to'
group 'Navigating Pry'
description 'Start a new subsession on a binding in the current stack.'
banner <<-'BANNER'
Start a new subsession on a binding in the current stack (numbered by nesting).
BANNER
def process(selection)
selection = selection.to_i
if selection < 0 || selection > pry_instance.binding_stack.size - 1
raise CommandError,
"Invalid binding index #{selection} - use `nesting` command " \
"to view valid indices."
else
Pry.start(pry_instance.binding_stack[selection])
end
end
end
Pry::Commands.add_command(Pry::Command::SwitchTo)
end
end
@@ -0,0 +1,28 @@
# frozen_string_literal: true
class Pry
class Command
class ToggleColor < Pry::ClassCommand
match 'toggle-color'
group 'Misc'
description 'Toggle syntax highlighting.'
banner <<-'BANNER'
Usage: toggle-color
Toggle syntax highlighting.
BANNER
def process
pry_instance.color = color_toggle
output.puts "Syntax highlighting #{pry_instance.color ? 'on' : 'off'}"
end
def color_toggle
!pry_instance.color
end
Pry::Commands.add_command(self)
end
end
end
@@ -0,0 +1,108 @@
# frozen_string_literal: true
class Pry
class Command
class WatchExpression < Pry::ClassCommand
match 'watch'
group 'Context'
description 'Watch the value of an expression and print a notification ' \
'whenever it changes.'
command_options use_prefix: false, state: %i[watch_expressions]
banner <<-'BANNER'
Usage: watch [EXPRESSION]
watch
watch --delete [INDEX]
watch [EXPRESSION] adds an expression to the list of those being watched.
It will be re-evaluated every time you hit enter in pry. If its value has
changed, the new value will be printed to the console.
This is useful if you are step-through debugging and want to see how
something changes over time. It's also useful if you're trying to write
a method inside pry and want to check that it gives the right answers
every time you redefine it.
watch on its own displays all the currently watched expressions and their
values, and watch --delete [INDEX] allows you to delete expressions from
the list being watched.
BANNER
def options(opt)
opt.on :d, :delete,
"Delete the watch expression with the given index. If no index " \
"is given; clear all watch expressions.",
optional_argument: true, as: Integer
opt.on :l, :list,
"Show all current watch expressions and their values. Calling " \
"watch with no expressions or options will also show the watch " \
"expressions."
end
def process
if opts.present?(:delete)
delete opts[:delete]
elsif opts.present?(:list) || args.empty?
list
else
add_hook
add_expression
end
end
private
def expressions
state.watch_expressions ||= []
end
def delete(index)
if index
output.puts "Deleting watch expression ##{index}: #{expressions[index - 1]}"
expressions.delete_at(index - 1)
else
output.puts "Deleting all watched expressions"
expressions.clear
end
end
def list
if expressions.empty?
output.puts "No watched expressions"
else
pry_instance.pager.open do |pager|
pager.puts "Listing all watched expressions:"
pager.puts ""
expressions.each_with_index do |expr, index|
pager.print with_line_numbers(expr.to_s, index + 1)
end
pager.puts ""
end
end
end
def eval_and_print_changed(output)
expressions.each do |expr|
expr.eval!
output.puts "#{blue 'watch'}: #{expr}" if expr.changed?
end
end
def add_expression
expressions << Expression.new(pry_instance, target, arg_string)
output.puts "Watching #{Code.new(arg_string).highlighted}"
end
def add_hook
hook = %i[after_eval watch_expression]
return if pry_instance.hooks.hook_exists?(*hook)
pry_instance.hooks.add_hook(*hook) do |_, pry_instance|
eval_and_print_changed pry_instance.output
end
end
end
Pry::Commands.add_command(Pry::Command::WatchExpression)
end
end
@@ -0,0 +1,43 @@
# frozen_string_literal: true
class Pry
class Command
class WatchExpression
class Expression
attr_reader :target, :source, :value, :previous_value, :pry_instance
def initialize(pry_instance, target, source)
@pry_instance = pry_instance
@target = target
@source = Code.new(source).strip
end
def eval!
@previous_value = value
@value = Pry::ColorPrinter.pp(target_eval(target, source), ''.dup)
end
def to_s
"#{Code.new(source).highlighted.strip} => #{value}"
end
# Has the value of the expression changed?
#
# We use the pretty-printed string representation to detect differences
# as this avoids problems with dup (causes too many differences) and ==
# (causes too few)
def changed?
(value != previous_value)
end
private
def target_eval(target, source)
target.eval(source)
rescue StandardError => e
e
end
end
end
end
end
@@ -0,0 +1,205 @@
# frozen_string_literal: true
require 'method_source'
class Pry
class Command
class Whereami < Pry::ClassCommand
def initialize(*)
super
@method_code = nil
end
class << self
attr_accessor :method_size_cutoff
end
@method_size_cutoff = 30
match 'whereami'
description 'Show code surrounding the current context.'
group 'Context'
banner <<-'BANNER'
Usage: whereami [-qn] [LINES]
Describe the current location. If you use `binding.pry` inside a method then
whereami will print out the source for that method.
If a number is passed, then LINES lines before and after the current line will be
shown instead of the method itself.
The `-q` flag can be used to suppress error messages in the case that there's
no code to show. This is used by pry in the default before_session hook to show
you when you arrive at a `binding.pry`.
The `-n` flag can be used to hide line numbers so that code can be copy/pasted
effectively.
When pry was started on an Object and there is no associated method, whereami
will instead output a brief description of the current object.
BANNER
def setup
if target.respond_to?(:source_location)
file, @line = target.source_location
@file = expand_path(file)
else
@file = expand_path(target.eval('__FILE__'))
@line = target.eval('__LINE__')
end
@method = Pry::Method.from_binding(target)
end
def options(opt)
opt.on :q, :quiet, "Don't display anything in case of an error"
opt.on :n, :"no-line-numbers", "Do not display line numbers"
opt.on :m, :method, "Show the complete source for the current method."
opt.on :c, :class, "Show the complete source for the current class or module."
opt.on :f, :file, "Show the complete source for the current file."
end
def code
@code ||= if opts.present?(:m)
method_code || raise(CommandError, "Cannot find method code.")
elsif opts.present?(:c)
class_code || raise(CommandError, "Cannot find class code.")
elsif opts.present?(:f)
Pry::Code.from_file(@file)
elsif args.any?
code_window
else
default_code
end
end
def code?
!!code
rescue MethodSource::SourceNotFoundError
false
end
def bad_option_combination?
[opts.present?(:m), opts.present?(:f),
opts.present?(:c), args.any?].count(true) > 1
end
def location
"#{@file}:#{@line} #{@method && @method.name_with_owner}"
end
def process
if bad_option_combination?
raise CommandError, "Only one of -m, -c, -f, and LINES may be specified."
end
return if nothing_to_do?
if internal_binding?(target)
handle_internal_binding
return
end
set_file_and_dir_locals(@file)
pretty_code = code.with_line_numbers(use_line_numbers?)
.with_marker(marker)
.highlighted
pry_instance.pager.page(
"\n#{bold('From:')} #{location}:\n\n" + pretty_code + "\n"
)
end
private
def nothing_to_do?
opts.quiet? && (internal_binding?(target) || !code?)
end
def use_line_numbers?
!opts.present?(:n)
end
def marker
!opts.present?(:n) && @line
end
def top_level?
target_self == Pry.main
end
def handle_internal_binding
if top_level?
output.puts "At the top level."
else
output.puts "Inside #{Pry.view_clip(target_self)}."
end
end
def small_method?
@method.source_range.count < self.class.method_size_cutoff
end
def default_code
if method_code && small_method?
method_code
else
code_window
end
end
def code_window
Pry::Code.from_file(@file).around(@line, window_size)
end
def method_code
return @method_code if @method_code
@method_code = Pry::Code.from_method(@method) if valid_method?
end
# This either returns the `target_self`
# or it returns the class of `target_self` if `target_self` is not a class.
# @return [Pry::WrappedModule]
def target_class
return Pry::WrappedModule(target_self) if target_self.is_a?(Module)
Pry::WrappedModule(target_self.class)
end
def class_code
@class_code ||=
begin
mod = @method ? Pry::WrappedModule(@method.owner) : target_class
idx = mod.candidates.find_index { |v| expand_path(v.source_file) == @file }
idx && Pry::Code.from_module(mod, idx)
end
end
def valid_method?
@method && @method.source? && expand_path(@method.source_file) == @file &&
@method.source_range.include?(@line)
end
def expand_path(filename)
return unless filename
return filename if Pry.eval_path == filename
File.expand_path(filename)
end
def window_size
if args.empty?
pry_instance.config.default_window_size
else
args.first.to_i
end
end
end
Pry::Commands.add_command(Pry::Command::Whereami)
Pry::Commands.alias_command '@', 'whereami'
Pry::Commands.alias_command(/whereami[!?]+/, 'whereami')
end
end
@@ -0,0 +1,95 @@
# frozen_string_literal: true
class Pry
class Command
class Wtf < Pry::ClassCommand
match(/wtf([?!]*)/)
group 'Context'
description 'Show the backtrace of the most recent exception.'
options listing: 'wtf?'
banner <<-'BANNER'
Usage: wtf[?|!]
Shows a few lines of the backtrace of the most recent exception (also available
as `_ex_.backtrace`). If you want to see more lines, add more question marks or
exclamation marks.
wtf?
wtf?!???!?!?
# To see the entire backtrace, pass the `-v` or `--verbose` flag.
wtf -v
BANNER
RUBY_FRAME_PATTERN = /\A(?<file>(.+)):(?<line>(\d+))/.freeze
def options(opt)
opt.on :v, :verbose, "Show the full backtrace"
opt.on :c, :code, "Show code corresponding to the backtrace frame"
end
def process
unless pry_instance.last_exception
raise Pry::CommandError, "No most-recent exception"
end
text = ''.dup
unwind_exceptions.each_with_index do |exception, i|
title = (i == 0 ? 'Exception' : 'Caused by')
text << format_header(title, exception)
text << format_backtrace(exception.backtrace)
end
output.puts(text)
end
private
def unwind_exceptions
exception_list = []
exception = pry_instance.last_exception
while exception
exception_list << exception
exception = (exception.cause if exception.respond_to?(:cause))
end
exception_list
end
def format_header(title, exception)
"#{bold(title + ':')} #{exception.class}: #{exception}\n--\n"
end
def format_backtrace(backtrace)
lines = trim_backtrace(backtrace).map do |frame|
next frame unless opts.code?
match = frame.match(RUBY_FRAME_PATTERN)
code = read_line(match[:file], match[:line].to_i)
[bold(frame), code].join("\n")
end
Pry::Code.new(lines.compact, 0, :text).with_line_numbers.to_s
end
def trim_backtrace(backtrace)
return backtrace if opts.verbose?
size_of_backtrace = [captures[0].size, 0.5].max * 10
backtrace.first(size_of_backtrace)
end
def read_line(file, line)
File.open(file, 'r') do |f|
(line - 1).times { f.gets }
f.gets
end
rescue Errno::ENOENT
nil
end
end
Pry::Commands.add_command(Pry::Command::Wtf)
end
end
+314
View File
@@ -0,0 +1,314 @@
# frozen_string_literal: true
class Pry
# @api private
class Config
extend Attributable
# @return [IO, #readline] the object from which Pry retrieves its lines of
# input
attribute :input
# @return [IO, #puts] where Pry should output results provided by {input}
attribute :output
# @return [Pry::CommandSet]
attribute :commands
# @return [Proc] the printer for Ruby expressions (not commands)
attribute :print
# @return [Proc] the printer for exceptions
attribute :exception_handler
# @return [Array] Exception that Pry shouldn't rescue
attribute :unrescued_exceptions
# @return [Integer] The number of lines of context to show before and after
# exceptions
attribute :default_window_size
# @return [Pry::Hooks]
attribute :hooks
# @return [Pry::Prompt]
attribute :prompt
# @return [String] The display name that is part of the prompt
attribute :prompt_name
# @return [Array<Object>] the list of objects that are known to have a
# 1-line #inspect output suitable for prompt
attribute :prompt_safe_contexts
# If it is a String, then that String is used as the shell
# command to invoke the editor.
#
# If it responds to #call is callable then `file`, `line`, and `reloading`
# are passed to it. `reloading` indicates whether Pry will be reloading code
# after the shell command returns. All parameters are optional.
# @return [String, #call]
attribute :editor
# A string that must precede all commands. For example, if is is
# set to "%", the "cd" command must be invoked as "%cd").
# @return [String]
attribute :command_prefix
# @return [Boolean]
attribute :color
# @return [Boolean]
attribute :pager
# @return [Boolean]
attribute :multiline
# @return [Boolean] whether the global ~/.pryrc should be loaded
attribute :should_load_rc
# @return [Boolean] whether the local ./.pryrc should be loaded
attribute :should_load_local_rc
# @return [Boolean] whether to load files specified with the -r flag
attribute :should_load_requires
# @return [Boolean] whether to disable edit-method's auto-reloading behavior
attribute :disable_auto_reload
# Whether Pry should trap SIGINT and cause it to raise an Interrupt
# exception. This is only useful on JRuby, MRI does this for us.
# @return [Boolean]
attribute :should_trap_interrupts
# @return [Pry::History]
attribute :history
# @return [Boolean]
attribute :history_save
# @return [Boolean]
attribute :history_load
# @return [String]
attribute :history_file
# @return [Array<String,Regexp>]
attribute :history_ignorelist
# @return [Array<String>] Ruby files to be required
attribute :requires
# @return [Integer] how many input/output lines to keep in memory
attribute :memory_size
# @return [Proc] The proc that runs system commands
attribute :system
# @return [Boolean]
attribute :auto_indent
# @return [Boolean]
attribute :correct_indent
# @return [Boolean] whether or not display a warning when a command name
# collides with a method/local in the current context.
attribute :collision_warning
# @return [Hash{Symbol=>Proc}]
attribute :extra_sticky_locals
# @return [#build_completion_proc] a completer to use
attribute :completer
# @return [Boolean] suppresses whereami output on `binding.pry`
attribute :quiet
# @return [Boolean] displays a warning about experience improvement on
# Windows
attribute :windows_console_warning
# @return [Proc]
attribute :command_completions
# @return [Proc]
attribute :file_completions
# @return [Hash]
attribute :ls
# @return [String] a line of code to execute in context before the session
# starts
attribute :exec_string
# @return [String]
attribute :output_prefix
# @return [String]
# @since v0.13.0
attribute :rc_file
def initialize
merge!(
input: MemoizedValue.new { choose_input },
output: $stdout.tap { |out| out.sync = true },
commands: Pry::Commands,
prompt_name: 'pry',
prompt: Pry::Prompt[:default],
prompt_safe_contexts: [String, Numeric, Symbol, nil, true, false],
print: Pry::ColorPrinter.method(:default),
quiet: false,
exception_handler: Pry::ExceptionHandler.method(:handle_exception),
unrescued_exceptions: [
::SystemExit, ::SignalException, Pry::TooSafeException
],
hooks: Pry::Hooks.default,
pager: true,
system: Pry::SystemCommandHandler.method(:default),
multiline: true,
color: Pry::Helpers::BaseHelpers.use_ansi_codes?,
default_window_size: 5,
editor: Pry::Editor.default,
rc_file: default_rc_file,
should_load_rc: true,
should_load_local_rc: true,
should_trap_interrupts: Pry::Helpers::Platform.jruby?,
disable_auto_reload: false,
command_prefix: '',
auto_indent: Pry::Helpers::BaseHelpers.use_ansi_codes?,
correct_indent: true,
collision_warning: false,
output_prefix: '=> ',
requires: [],
should_load_requires: true,
windows_console_warning: true,
control_d_handler: Pry::ControlDHandler.method(:default),
memory_size: 100,
extra_sticky_locals: {},
command_completions: proc { commands.keys },
file_completions: proc { Dir['.'] },
ls: Pry::Command::Ls::Config.default,
completer: Pry::InputCompleter,
history_save: true,
history_load: true,
history_file: Pry::History.default_file,
history_ignorelist: [],
history: MemoizedValue.new do
if defined?(input::HISTORY)
Pry::History.new(history: input::HISTORY)
else
Pry::History.new
end
end,
exec_string: ''
)
@custom_attrs = {}
end
def merge!(config_hash)
config_hash.each_pair { |attr, value| __send__("#{attr}=", value) }
self
end
def merge(config_hash)
dup.merge!(config_hash)
end
def []=(attr, value)
@custom_attrs[attr.to_s] = Config::Value.new(value)
end
def [](attr)
@custom_attrs[attr.to_s].call
end
# rubocop:disable Style/MethodMissingSuper
def method_missing(method_name, *args, &_block)
name = method_name.to_s
if name.end_with?('=')
self[name[0..-2]] = args.first
elsif @custom_attrs.key?(name)
self[name]
end
end
# rubocop:enable Style/MethodMissingSuper
def respond_to_missing?(method_name, include_all = false)
@custom_attrs.key?(method_name.to_s.tr('=', '')) || super
end
def initialize_dup(other)
super
@custom_attrs = @custom_attrs.dup
end
attr_reader :control_d_handler
def control_d_handler=(value)
proxy_proc =
if value.arity == 2
Pry::Warning.warn(
"control_d_handler's arity of 2 parameters was deprecated " \
'(eval_string, pry_instance). Now it gets passed just 1 ' \
'parameter (pry_instance)'
)
proc do |*args|
if args.size == 2
value.call(args.first, args[1])
else
value.call(args.first.eval_string, args.first)
end
end
else
proc do |*args|
if args.size == 2
value.call(args[1])
else
value.call(args.first)
end
end
end
@control_d_handler = proxy_proc
end
private
def choose_input
input = load_readline
if Pry::Env['TERM'] == 'dumb' && (defined?(Reline) && input == Reline)
input = Pry::Input::SimpleStdio
end
input
end
def load_readline
require 'readline'
::Readline
rescue LoadError
output.puts(
"Sorry, you can't use Pry without Readline or a compatible library. \n" \
"Possible solutions: \n" \
" * Rebuild Ruby with Readline support using `--with-readline` \n" \
" * Use the rb-readline gem, which is a pure-Ruby port of Readline \n" \
" * Use the pry-coolline gem, a pure-ruby alternative to Readline"
)
raise
end
def default_rc_file
[Pry::Env['PRYRC'],
# See XDG Base Directory Specification at
# https://specifications.freedesktop.org/basedir-spec/latest/
"#{Pry::Env['XDG_CONFIG_HOME']}/pry/pryrc",
File.expand_path('~/.pryrc'),
File.expand_path('~/.config/pry/pryrc')]
.compact
.find { |file| File.exist?(file) }
end
end
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
class Pry
class Config
# Attributable provides the ability to create "attribute"
# accessors. Attribute accessors create a standard "attr_writer" and a
# customised "attr_reader". This reader is Proc-aware (lazy).
#
# @since v0.13.0
# @api private
module Attributable
def attribute(attr_name)
define_method(attr_name) do
value = Config::Value.new(instance_variable_get("@#{attr_name}"))
value.call
end
attr_writer(attr_name)
end
end
end
end
@@ -0,0 +1,29 @@
# frozen_string_literal: true
class Pry
class Config
# LazyValue is a Proc (block) wrapper. It is meant to be used as a
# configuration value. Subsequent `#call` calls always evaluate the given
# block.
#
# @example
# num = 19
# value = Pry::Config::LazyValue.new { num += 1 }
# value.foo # => 20
# value.foo # => 21
# value.foo # => 22
#
# @api private
# @since v0.13.0
# @see Pry::Config::MemoizedValue
class LazyValue
def initialize(&block)
@block = block
end
def call
@block.call
end
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
class Pry
class Config
# MemoizedValue is a Proc (block) wrapper. It is meant to be used as a
# configuration value. Subsequent `#call` calls return the same memoized
# result.
#
# @example
# num = 19
# value = Pry::Config::MemoizedValue.new { num += 1 }
# value.call # => 20
# value.call # => 20
# value.call # => 20
#
# @api private
# @since v0.13.0
# @see Pry::Config::LazyValue
class MemoizedValue
def initialize(&block)
@block = block
@called = false
@call = nil
end
def call
return @call if @called
@called = true
@call = @block.call
end
end
end
end
@@ -0,0 +1,24 @@
# frozen_string_literal: true
class Pry
class Config
# Value holds a value for the given attribute and decides how it should
# be read. Procs get called, other values are returned as is.
#
# @since v0.13.0
# @api private
class Value
def initialize(value)
@value = value
end
def call
unless [Config::MemoizedValue, Config::LazyValue].include?(@value.class)
return @value
end
@value.call
end
end
end
end
@@ -0,0 +1,28 @@
# frozen_string_literal: true
class Pry
# @api private
# @since v0.13.0
module ControlDHandler
# Deal with the ^D key being pressed. Different behaviour in different
# cases:
# 1. In an expression behave like `!` command.
# 2. At top-level session behave like `exit` command.
# 3. In a nested session behave like `cd ..`.
def self.default(pry_instance)
if !pry_instance.eval_string.empty?
# Clear input buffer.
pry_instance.eval_string = ''
elsif pry_instance.binding_stack.one?
pry_instance.binding_stack.clear
throw(:breakout)
else
# Otherwise, saves current binding stack as old stack and pops last
# binding out of binding stack (the old stack still has that binding).
cd_state = Pry::CommandState.default.state_for(Pry::Command::Cd)
cd_state.old_stack = pry_instance.binding_stack.dup
pry_instance.binding_stack.pop
end
end
end
end
@@ -0,0 +1,144 @@
# frozen_string_literal: true
class Pry
# @return [Array] Code of the method used when implementing Pry's
# __binding__, along with line indication to be used with instance_eval (and
# friends).
#
# @see Object#__binding__
BINDING_METHOD_IMPL = [<<-METHOD, __FILE__, __LINE__ + 1].freeze
# Get a binding with 'self' set to self, and no locals.
#
# The default definee is determined by the context in which the
# definition is eval'd.
#
# Please don't call this method directly, see {__binding__}.
#
# @return [Binding]
def __pry__
binding
end
METHOD
end
class Object
# Start a Pry REPL on self.
#
# If `self` is a Binding then that will be used to evaluate expressions;
# otherwise a new binding will be created.
#
# @param [Object] object the object or binding to pry
# (__deprecated__, use `object.pry`)
# @param [Hash] hash the options hash
# @example With a binding
# binding.pry
# @example On any object
# "dummy".pry
# @example With options
# def my_method
# binding.pry :quiet => true
# end
# my_method()
# @see Pry.start
def pry(object = nil, hash = {})
if object.nil? || Hash === object # rubocop:disable Style/CaseEquality
Pry.start(self, object || {})
else
Pry.start(object, hash)
end
end
# Return a binding object for the receiver.
#
# The `self` of the binding is set to the current object, and it contains no
# local variables.
#
# The default definee (http://yugui.jp/articles/846) is set such that:
#
# * If `self` is a class or module, then new methods created in the binding
# will be defined in that class or module (as in `class Foo; end`).
# * If `self` is a normal object, then new methods created in the binding will
# be defined on its singleton class (as in `class << self; end`).
# * If `self` doesn't have a real singleton class (i.e. it is a Fixnum, Float,
# Symbol, nil, true, or false), then new methods will be created on the
# object's class (as in `self.class.class_eval{ }`)
#
# Newly created constants, including classes and modules, will also be added
# to the default definee.
#
# @return [Binding]
def __binding__
# If you ever feel like changing this method, be careful about variables
# that you use. They shouldn't be inserted into the binding that will
# eventually be returned.
# When you're cd'd into a class, methods you define should be added to it.
if is_a?(Module)
# A special case, for JRuby.
# Module.new.class_eval("binding") has different behaviour than CRuby,
# where this is not needed: class_eval("binding") vs class_eval{binding}.
# Using a block works around the difference of behaviour on JRuby.
# The scope is clear of local variables. Don't add any.
#
# This fixes the following two spec failures, at https://travis-ci.org/pry/pry/jobs/274470002
# 1) ./spec/pry_spec.rb:360:in `block in (root)'
# 2) ./spec/pry_spec.rb:366:in `block in (root)'
return class_eval { binding } if Pry::Helpers::Platform.jruby? && name.nil?
# class_eval sets both self and the default definee to this class.
return class_eval("binding", __FILE__, __LINE__)
end
unless self.class.method_defined?(:__pry__)
# The easiest way to check whether an object has a working singleton class
# is to try and define a method on it. (just checking for the presence of
# the singleton class gives false positives for `true` and `false`).
# __pry__ is just the closest method we have to hand, and using
# it has the nice property that we can memoize this check.
begin
# instance_eval sets the default definee to the object's singleton class
instance_eval(*Pry::BINDING_METHOD_IMPL)
# If we can't define methods on the Object's singleton_class. Then we fall
# back to setting the default definee to be the Object's class. That seems
# nicer than having a REPL in which you can't define methods.
rescue TypeError, Pry::FrozenObjectException
# class_eval sets the default definee to self.class
self.class.class_eval(*Pry::BINDING_METHOD_IMPL)
end
end
__pry__
end
end
class BasicObject
# Return a binding object for the receiver.
#
# The `self` of the binding is set to the current object, and it contains no
# local variables.
#
# The default definee (http://yugui.jp/articles/846) is set such that new
# methods defined will be added to the singleton class of the BasicObject.
#
# @return [Binding]
def __binding__
# BasicObjects don't have respond_to?, so we just define the method
# every time. As they also don't have `.freeze`, this call won't
# fail as it can for normal Objects.
(class << self; self; end).class_eval(<<-METHOD, __FILE__, __LINE__ + 1)
# Get a binding with 'self' set to self, and no locals.
#
# The default definee is determined by the context in which the
# definition is eval'd.
#
# Please don't call this method directly, see {__binding__}.
#
# @return [Binding]
def __pry__
::Kernel.binding
end
METHOD
__pry__
end
end
+157
View File
@@ -0,0 +1,157 @@
# frozen_string_literal: true
require 'shellwords'
class Pry
class Editor
def self.default
if (visual = Pry::Env['VISUAL'])
return visual
end
if (editor = Pry::Env['EDITOR'])
return editor
end
return 'notepad' if Helpers::Platform.windows?
%w[editor nano vi].find do |editor_exe|
Kernel.system("which #{editor_exe} > /dev/null 2>&1")
end
end
include Pry::Helpers::CommandHelpers
attr_reader :pry_instance
def initialize(pry_instance)
@pry_instance = pry_instance
end
def edit_tempfile_with_content(initial_content, line = 1)
temp_file do |f|
f.puts(initial_content)
f.flush
f.close(false)
invoke_editor(f.path, line, true)
File.read(f.path)
end
end
def invoke_editor(file, line, blocking = true)
unless pry_instance.config.editor
raise CommandError,
"Please set Pry.config.editor or export $VISUAL or $EDITOR"
end
editor_invocation = build_editor_invocation_string(file, line, blocking)
return nil unless editor_invocation
if Helpers::Platform.jruby?
open_editor_on_jruby(editor_invocation)
else
open_editor(editor_invocation)
end
end
# Generate the string that's used to start the editor. This includes
# all the flags we want as well as the file and line number we
# want to open at.
def build_editor_invocation_string(file, line, blocking)
if pry_instance.config.editor.respond_to?(:call)
args = [file, line, blocking][0...(pry_instance.config.editor.arity)]
pry_instance.config.editor.call(*args)
else
sanitized_file = Helpers::Platform.windows? ? file : Shellwords.escape(file)
editor = pry_instance.config.editor
flag = blocking_flag_for_editor(blocking)
start_line = start_line_syntax_for_editor(sanitized_file, line)
"#{editor} #{flag} #{start_line}"
end
end
private
# Start the editor running, using the calculated invocation string
def open_editor(editor_invocation)
# Note we dont want to use Pry.config.system here as that
# may be invoked non-interactively (i.e via Open4), whereas we want to
# ensure the editor is always interactive
system(*Shellwords.split(editor_invocation)) ||
raise(
CommandError,
"`#{editor_invocation}` gave exit status: #{$CHILD_STATUS.exitstatus}"
)
end
# We need JRuby specific code here cos just shelling out using
# system() appears to be pretty broken :/
def open_editor_on_jruby(editor_invocation)
require 'spoon'
pid = Spoon.spawnp(*Shellwords.split(editor_invocation))
Process.waitpid(pid)
rescue FFI::NotFoundError
system(editor_invocation)
end
# Some editors that run outside the terminal allow you to control whether or
# not to block the process from which they were launched (in this case, Pry).
# For those editors, return the flag that produces the desired behavior.
def blocking_flag_for_editor(blocking)
case editor_name
when /^emacsclient/
'--no-wait' unless blocking
when /^[gm]vim/
'--nofork' if blocking
when /^jedit/
'-wait' if blocking
when /^mate/, /^subl/, /^redcar/, /^code/
'-w' if blocking
end
end
# Return the syntax for a given editor for starting the editor
# and moving to a particular line within that file
def start_line_syntax_for_editor(file_name, line_number)
# special case for 1st line
return file_name if line_number <= 1
case editor_name
when /^[gm]?vi/, /^emacs/, /^nano/, /^pico/, /^gedit/, /^kate/
"+#{line_number} #{file_name}"
when /^mate/, /^geany/
"-l #{line_number} #{file_name}"
when /^code/
"-g #{file_name}:#{line_number}"
when /^subl/
"#{file_name}:#{line_number}"
when /^uedit32/
"#{file_name}/#{line_number}"
when /^jedit/
"#{file_name} +line:#{line_number}"
when /^redcar/
"-l#{line_number} #{file_name}"
else
if Helpers::Platform.windows?
file_name.to_s
else
"+#{line_number} #{file_name}"
end
end
end
# Get the name of the binary that Pry.config.editor points to.
#
# This is useful for deciding which flags we pass to the editor as
# we can just use the program's name and ignore any absolute paths.
#
# @example
# Pry.config.editor="/home/conrad/bin/textmate -w"
# editor_name
# # => textmate
#
def editor_name
File.basename(pry_instance.config.editor).split(" ").first
end
end
end
+18
View File
@@ -0,0 +1,18 @@
# frozen_string_literal: true
class Pry
# Env is a helper module to work with environment variables.
#
# @since v0.13.0
# @api private
module Env
def self.[](key)
return unless ENV.key?(key)
value = ENV[key]
return if value == ''
value
end
end
end
@@ -0,0 +1,48 @@
# frozen_string_literal: true
class Pry
# @api private
# @since v0.13.0
module ExceptionHandler
class << self
# Will only show the first line of the backtrace.
def handle_exception(output, exception, _pry_instance)
if exception.is_a?(UserError) && exception.is_a?(SyntaxError)
output.puts "SyntaxError: #{exception.message.sub(/.*syntax error, */m, '')}"
else
output.puts standard_error_text_for(exception)
end
end
private
def standard_error_text_for(exception)
text = exception_text(exception)
return text unless exception.respond_to?(:cause)
cause = exception.cause
while cause
text += cause_text(cause)
cause = cause.cause
end
text
end
def exception_text(exception)
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.2')
"#{exception.class}: #{exception.message}\n" \
"from #{exception.backtrace.first}\n"
else
"#{exception.class}: #{exception.detailed_message}\n" \
"from #{exception.backtrace.first}\n"
end
end
def cause_text(cause)
"Caused by #{cause.class}: #{cause}\n" \
"from #{cause.backtrace.first}\n"
end
end
end
end
+73
View File
@@ -0,0 +1,73 @@
# frozen_string_literal: true
class Pry
# As a REPL, we often want to catch any unexpected exceptions that may have
# been raised; however we don't want to go overboard and prevent the user
# from exiting Pry when they want to.
module RescuableException
def self.===(exception)
case exception
# Catch when the user hits ^C (Interrupt < SignalException), and assume
# that they just wanted to stop the in-progress command (just like bash
# etc.)
when Interrupt
true
# Don't catch signals (particularly not SIGTERM) as these are unlikely
# to be intended for pry itself. We should also make sure that
# Kernel#exit works.
when *Pry.config.unrescued_exceptions
false
# All other exceptions will be caught.
else
true
end
end
end
# Catches SecurityErrors if $SAFE is set
module TooSafeException
def self.===(exception)
if Pry::HAS_SAFE_LEVEL
$SAFE > 0 && exception.is_a?(SecurityError)
else
exception.is_a?(SecurityError)
end
end
end
# An Exception Tag (cf. Exceptional Ruby) that instructs Pry to show the error
# in a more user-friendly manner. This should be used when the exception
# happens within Pry itself as a direct consequence of the user typing
# something wrong.
#
# This allows us to distinguish between the user typing:
#
# pry(main)> def )
# SyntaxError: unexpected )
#
# pry(main)> method_that_evals("def )")
# SyntaxError: (eval):1: syntax error, unexpected ')'
# from ./a.rb:2 in `eval'
module UserError; end
# When we try to get a binding for an object, we try to define a method on
# that Object's singleton class. This doesn't work for "frozen" Object's, and
# the exception is just a vanilla RuntimeError.
module FrozenObjectException
def self.===(exception)
[
"can't modify frozen class/module",
"can't modify frozen Class",
"can't modify frozen object"
].include?(exception.message)
end
end
# CommandErrors are caught by the REPL loop and displayed to the user. They
# indicate an exceptional condition that's fatal to the current command.
class CommandError < StandardError; end
class MethodNotFound < CommandError; end
# indicates obsolete API
class ObsoleteError < StandardError; end
end
@@ -0,0 +1,27 @@
# frozen_string_literal: true
class Pry
module Forwardable
require 'forwardable'
include ::Forwardable
#
# Since Ruby 2.4, Forwardable will print a warning when
# calling a method that is private on a delegate, and
# in the future it could be an error: https://bugs.ruby-lang.org/issues/12782#note-3
#
# That's why we revert to a custom implementation for delegating one
# private method to another.
#
def def_private_delegators(target, *private_delegates)
private_delegates.each do |private_delegate|
define_method(private_delegate) do |*a, &b|
instance_variable_get(target).__send__(private_delegate, *a, &b)
end
end
class_eval do
private(*private_delegates) # rubocop:disable Style/AccessModifierDeclarations
end
end
end
end
+8
View File
@@ -0,0 +1,8 @@
# frozen_string_literal: true
require "pry/helpers/base_helpers"
require "pry/helpers/options_helpers"
require "pry/helpers/command_helpers"
require "pry/helpers/text"
require "pry/helpers/table"
require "pry/helpers/platform"
@@ -0,0 +1,71 @@
# frozen_string_literal: true
class Pry
module Helpers
module BaseHelpers
extend self
def silence_warnings
old_verbose = $VERBOSE
$VERBOSE = nil
begin
yield
ensure
$VERBOSE = old_verbose
end
end
# Acts like send but ignores any methods defined below Object or Class in the
# inheritance hierarchy.
# This is required to introspect methods on objects like Net::HTTP::Get that
# have overridden the `method` method.
def safe_send(obj, method, *args, &block)
(obj.is_a?(Module) ? Module : Object).instance_method(method)
.bind(obj).call(*args, &block)
end
def find_command(name, set = Pry::Commands)
command_match = set.find do |_, command|
(listing = command.options[:listing]) == name && !listing.nil?
end
command_match.last if command_match
end
def not_a_real_file?(file)
file =~ /^(\(.*\))$|^<.*>$/ || file =~ /__unknown__/ || file == "" || file == "-e"
end
def use_ansi_codes?
Pry::Helpers::Platform.windows_ansi? ||
((term = Pry::Env['TERM']) && term != "dumb")
end
def colorize_code(code)
SyntaxHighlighter.highlight(code)
end
def highlight(string, regexp, highlight_color = :bright_yellow)
string.gsub(regexp) do |match|
"<#{highlight_color}>#{match}</#{highlight_color}>"
end
end
# formatting
def heading(text)
text = "#{text}\n--"
"\e[1m#{text}\e[0m"
end
# Send the given text through the best available pager (if Pry.config.pager is
# enabled). Infers where to send the output if used as a mixin.
# DEPRECATED.
def stagger_output(text, _out = nil)
if defined?(pry_instance) && pry_instance
pry_instance.pager.page text
else
Pry.new.pager.page text
end
end
end
end
end
@@ -0,0 +1,146 @@
# frozen_string_literal: true
require 'tempfile'
class Pry
module Helpers
module CommandHelpers
include OptionsHelpers
extend self
# Open a temp file and yield it to the block, closing it after
# @return [String] The path of the temp file
def temp_file(ext = '.rb')
file = Tempfile.new(['pry', ext])
yield(file)
ensure
file.close(true)
end
def internal_binding?(context)
method_name = context.eval("::Kernel.__method__").to_s
# class_eval is here because of http://jira.codehaus.org/browse/JRUBY-6753
%w[__binding__ __pry__ class_eval].include?(method_name)
# TODO: codehaus is dead, there was no test for this and the
# description for the commit doesn't exist. Probably a candidate for
# removal so we have a chance to introduce a regression and document it
# properly.
end
def get_method_or_raise(method_name, context, opts = {})
method = Pry::Method.from_str(method_name, context, opts)
if !method && method_name
raise Pry::MethodNotFound, "method '#{method_name}' could not be found."
end
(opts[:super] || 0).times do
if method.super
method = method.super
else
raise Pry::MethodNotFound,
"'#{method.name_with_owner}' has no super method"
end
end
if !method || (!method_name && internal_binding?(context))
raise Pry::MethodNotFound,
'no method name given, and context is not a method'
end
set_file_and_dir_locals(method.source_file)
method
end
# Remove any common leading whitespace from every line in `text`. This
# can be used to make a HEREDOC line up with the left margin, without
# sacrificing the indentation level of the source code.
#
# @example
# opt.banner(unindent(<<-USAGE))
# Lorem ipsum dolor sit amet, consectetur adipisicing elit,
# sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
# "Ut enim ad minim veniam."
# USAGE
#
# @param [String] dirty_text The text from which to remove indentation
# @return [String] the text with indentation stripped
def unindent(dirty_text, left_padding = 0)
text = dirty_text.sub(/\A[ \t]+\z/, '') # Empty blank lines.
# Find the longest common whitespace to all indented lines. Ignore lines
# containing just -- or ++ as these seem to be used by comment authors
# as delimiters.
scanned_text = text.scan(/^[ \t]*(?!--\n|\+\+\n)(?=[^ \t\n])/)
margin = scanned_text.inject do |current_margin, next_indent|
if next_indent.start_with?(current_margin)
current_margin
elsif current_margin.start_with?(next_indent)
next_indent
else
''
end
end
text.gsub(/^#{margin}/, ' ' * left_padding)
end
# Restrict a string to the given range of lines (1-indexed)
# @param [String] content The string.
# @param [Range, Integer] lines The line(s) to restrict it to.
# @return [String] The resulting string.
def restrict_to_lines(content, lines)
line_range = one_index_range_or_number(lines)
Array(content.lines.to_a[line_range]).join
end
def one_index_number(line_number)
line_number > 0 ? line_number - 1 : line_number
end
# convert a 1-index range to a 0-indexed one
def one_index_range(range)
Range.new(one_index_number(range.begin), one_index_number(range.end))
end
def one_index_range_or_number(range_or_number)
case range_or_number
when Range
one_index_range(range_or_number)
else
one_index_number(range_or_number)
end
end
def absolute_index_number(line_number, array_length)
if line_number >= 0
line_number
else
[array_length + line_number, 0].max
end
end
def absolute_index_range(range_or_number, array_length)
case range_or_number
when Range
a = absolute_index_number(range_or_number.begin, array_length)
b = absolute_index_number(range_or_number.end, array_length)
else
a = b = absolute_index_number(range_or_number, array_length)
end
Range.new(a, b)
end
def set_file_and_dir_locals(file_name, pry = pry_instance, ctx = target)
return if !ctx || !file_name
pry.last_file = File.expand_path(file_name)
pry.inject_local("_file_", pry.last_file, ctx)
pry.last_dir = File.dirname(pry.last_file)
pry.inject_local("_dir_", pry.last_dir, ctx)
end
end
end
end
@@ -0,0 +1,84 @@
# frozen_string_literal: true
class Pry
module Helpers
# This class contains methods useful for extracting
# documentation from methods and classes.
module DocumentationHelpers
YARD_TAGS = %w[
param return option yield attr attr_reader attr_writer deprecate example
raise
].freeze
module_function
def process_rdoc(comment)
comment = comment.dup
last_match_ruby = proc do
SyntaxHighlighter.highlight(Regexp.last_match(1))
end
comment.gsub(%r{<code>(?:\s*\n)?(.*?)\s*</code>}m, &last_match_ruby)
.gsub(%r{<em>(?:\s*\n)?(.*?)\s*</em>}m) { "\e[1m#{Regexp.last_match(1)}\e[0m" }
.gsub(%r{<i>(?:\s*\n)?(.*?)\s*</i>}m) { "\e[1m#{Regexp.last_match(1)}\e[0m" }
.gsub(%r{<tt>(?:\s*\n)?(.*?)\s*</tt>}m, &last_match_ruby)
.gsub(/\B\+(\w+?)\+\B/) { "\e[32m#{Regexp.last_match(1)}\e[0m" }
.gsub(/((?:^[ \t]+(?:(?!.+\e\[)).+(?:\n+|\Z))+)/, &last_match_ruby)
.gsub(/`(?:\s*\n)?([^\e]*?)\s*`/) { "`#{last_match_ruby.call}`" }
end
def process_yardoc_tag(comment, tag)
in_tag_block = nil
comment.lines.map do |v|
if in_tag_block && v !~ /^\S/
Pry::Helpers::Text.strip_color Pry::Helpers::Text.strip_color(v)
elsif in_tag_block
in_tag_block = false
v
else
in_tag_block = true if v =~ /^@#{tag}/
v
end
end.join
end
def process_yardoc(comment)
(YARD_TAGS - %w[example])
.inject(comment) { |a, v| process_yardoc_tag(a, v) }
.gsub(/^@(#{YARD_TAGS.join("|")})/) { "\e[33m#{Regexp.last_match(1)}\e[0m" }
end
def process_comment_markup(comment)
process_yardoc process_rdoc(comment)
end
# @param [String] code
# @return [String]
def strip_comments_from_c_code(code)
code.sub(%r{\A\s*/\*.*?\*/\s*}m, '')
end
# Given a string that makes up a comment in a source-code file parse out the content
# that the user is intended to read. (i.e. without leading indentation, #-characters
# or shebangs)
#
# @param [String] comment
# @return [String]
def get_comment_content(comment)
comment = comment.dup
# Remove #!/usr/bin/ruby
comment.gsub!(/\A\#!.*$/, '')
# Remove leading empty comment lines
comment.gsub!(/\A\#+?$/, '')
comment.gsub!(/^\s*#/, '')
strip_leading_whitespace(comment)
end
# @param [String] text
# @return [String]
def strip_leading_whitespace(text)
Pry::Helpers::CommandHelpers.unindent(text)
end
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
class Pry
module Helpers
module OptionsHelpers
module_function
# Add method options to the Pry::Slop instance
def method_options(opt)
@method_target = target
opt.on :M, "instance-methods", "Operate on instance methods."
opt.on :m, :methods, "Operate on methods."
opt.on :s, :super, "Select the 'super' method. Can be repeated to " \
"traverse the ancestors.", as: :count
opt.on(
:c, :context, "Select object context to run under.", argument: true
) do |context|
@method_target = Pry.binding_for(target.eval(context))
end
end
# Get the method object parsed by the slop instance
def method_object
@method_object ||= get_method_or_raise(
args.empty? ? nil : args.join(" "),
@method_target,
super: opts[:super],
instance: opts.present?(:'instance-methods') && !opts.present?(:methods),
methods: opts.present?(:methods) && !opts.present?(:'instance-methods')
)
end
end
end
end
@@ -0,0 +1,55 @@
# frozen_string_literal: true
require 'rbconfig'
class Pry
module Helpers
# Contains methods for querying the platform that Pry is running on
# @api public
# @since v0.12.0
module Platform
# @return [Boolean]
def self.mac_osx?
!!(RbConfig::CONFIG['host_os'] =~ /\Adarwin/i)
end
# @return [Boolean]
def self.linux?
!!(RbConfig::CONFIG['host_os'] =~ /linux/i)
end
# @return [Boolean] true when Pry is running on Windows with ANSI support,
# false otherwise
def self.windows?
!!(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/)
end
# @return [Boolean]
def self.windows_ansi?
return false unless windows?
!!(defined?(Win32::Console) || Pry::Env['ANSICON'] || mri?)
end
# @return [Boolean]
def self.jruby?
RbConfig::CONFIG['ruby_install_name'] == 'jruby'
end
# @return [Boolean]
def self.jruby_19?
jruby? && RbConfig::CONFIG['ruby_version'] == '1.9'
end
# @return [Boolean]
def self.mri?
RbConfig::CONFIG['ruby_install_name'] == 'ruby'
end
# @return [Boolean]
def self.mri_2?
mri? && RUBY_VERSION.start_with?('2')
end
end
end
end
@@ -0,0 +1,121 @@
# frozen_string_literal: true
class Pry
module Helpers
def self.tablify_or_one_line(heading, things, pry_instance = Pry.new)
plain_heading = Pry::Helpers::Text.strip_color(heading)
attempt = Table.new(things, column_count: things.size)
if attempt.fits_on_line?(pry_instance.output.width - plain_heading.size - 2)
"#{heading}: #{attempt}\n"
else
content = tablify_to_screen_width(things, { indent: ' ' }, pry_instance)
"#{heading}: \n#{content}\n"
end
end
def self.tablify_to_screen_width(things, options, pry_instance = Pry.new)
options ||= {}
things = things.compact
if (indent = options[:indent])
usable_width = pry_instance.output.width - indent.size
tablify(things, usable_width, pry_instance).to_s.gsub(/^/, indent)
else
tablify(things, pry_instance.output.width, pry_instance).to_s
end
end
def self.tablify(things, line_length, pry_instance = Pry.new)
table = Table.new(things, { column_count: things.size }, pry_instance)
until (table.column_count == 1) || table.fits_on_line?(line_length)
table.column_count -= 1
end
table
end
class Table
attr_reader :items, :column_count
def initialize(items, args, pry_instance = Pry.new)
@column_count = args[:column_count]
@config = pry_instance.config
self.items = items
end
def to_s
rows_to_s.join("\n")
end
def rows_to_s(style = :color_on)
widths = columns.map { |e| _max_width(e) }
@rows_without_colors.map do |r|
padded = []
r.each_with_index do |e, i|
next unless e
item = e.ljust(widths[i])
item.sub! e, _recall_color_for(e) if style == :color_on
padded << item
end
padded.join(@config.ls.separator)
end
end
def items=(items)
@items = items
_rebuild_colorless_cache
_recolumn
end
def column_count=(count)
@column_count = count
_recolumn
end
def fits_on_line?(line_length)
_max_width(rows_to_s(:no_color)) <= line_length
end
def columns
@rows_without_colors.transpose
end
def ==(other)
items == other.to_a
end
def to_a
items.to_a
end
private
def _max_width(things)
things.compact.map(&:size).max || 0
end
def _rebuild_colorless_cache
@colorless_cache = {}
@plain_items = []
items.map do |e|
plain = Pry::Helpers::Text.strip_color(e)
@colorless_cache[plain] = e
@plain_items << plain
end
end
def _recolumn
@rows_without_colors = []
return if items.size.zero?
row_count = (items.size.to_f / column_count).ceil
row_count.times do |i|
row_indices = (0...column_count).map { |e| row_count * e + i }
@rows_without_colors << row_indices.map { |e| @plain_items[e] }
end
end
def _recall_color_for(thing)
@colorless_cache[thing]
end
end
end
end
+118
View File
@@ -0,0 +1,118 @@
# frozen_string_literal: true
class Pry
module Helpers
# The methods defined on {Text} are available to custom commands via
# {Pry::Command#text}.
module Text
extend self
COLORS = {
"black" => 0,
"red" => 1,
"green" => 2,
"yellow" => 3,
"blue" => 4,
"purple" => 5,
"magenta" => 5,
"cyan" => 6,
"white" => 7
}.freeze
COLORS.each_pair do |color, value|
define_method color do |text|
"\033[0;#{30 + value}m#{text}\033[0m"
end
define_method "bright_#{color}" do |text|
"\033[1;#{30 + value}m#{text}\033[0m"
end
COLORS.each_pair do |bg_color, bg_value|
define_method "#{color}_on_#{bg_color}" do |text|
"\033[0;#{30 + value};#{40 + bg_value}m#{text}\033[0m"
end
define_method "bright_#{color}_on_#{bg_color}" do |text|
"\033[1;#{30 + value};#{40 + bg_value}m#{text}\033[0m"
end
end
end
# Remove any color codes from _text_.
#
# @param [String, #to_s] text
# @return [String] _text_ stripped of any color codes.
def strip_color(text)
text.to_s.gsub(/(\001)?(\e\[(\d[;\d]?)*m)(\002)?/, '')
end
# Returns _text_ as bold text for use on a terminal.
#
# @param [String, #to_s] text
# @return [String] _text_
def bold(text)
"\e[1m#{text}\e[0m"
end
# Returns `text` in the default foreground colour.
# Use this instead of "black" or "white" when you mean absence of colour.
#
# @param [String, #to_s] text
# @return [String]
def default(text)
text.to_s
end
#
# @yield
# Yields a block with color turned off.
#
# @return [void]
#
def no_color
boolean = Pry.config.color
Pry.config.color = false
yield
ensure
Pry.config.color = boolean
end
#
# @yield
# Yields a block with paging turned off.
#
# @return [void]
#
def no_pager
boolean = Pry.config.pager
Pry.config.pager = false
yield
ensure
Pry.config.pager = boolean
end
# Returns _text_ in a numbered list, beginning at _offset_.
#
# @param [#each_line] text
# @param [Fixnum] offset
# @return [String]
def with_line_numbers(text, offset, color = :blue)
lines = text.each_line.to_a
max_width = (offset + lines.count).to_s.length
lines.each_with_index.map do |line, index|
adjusted_index = (index + offset).to_s.rjust(max_width)
"#{send(color, adjusted_index)}: #{line}"
end.join
end
# Returns _text_ indented by _chars_ spaces.
#
# @param [String] text
# @param [Fixnum] chars
def indent(text, chars)
text.lines.map { |l| "#{' ' * chars}#{l}" }.join
end
end
end
end
+153
View File
@@ -0,0 +1,153 @@
# frozen_string_literal: true
class Pry
# The History class is responsible for maintaining the user's input history,
# both internally and within Readline.
class History
def self.default_file
history_file =
if (xdg_home = Pry::Env['XDG_DATA_HOME'])
# See XDG Base Directory Specification at
# https://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html
xdg_home + '/pry/pry_history'
elsif File.exist?(File.expand_path('~/.pry_history'))
'~/.pry_history'
else
'~/.local/share/pry/pry_history'
end
File.expand_path(history_file)
end
attr_accessor :loader, :saver
# @return [Fixnum] Number of lines in history when Pry first loaded.
attr_reader :original_lines
# @return [Integer] total number of lines, including original lines
attr_reader :history_line_count
def initialize(options = {})
@history = options[:history] || []
@history_line_count = @history.count
@file_path = options[:file_path]
@original_lines = 0
@loader = method(:read_from_file)
@saver = method(:save_to_file)
end
# Load the input history using `History.loader`.
# @return [Integer] The number of lines loaded
def load
@loader.call do |line|
next if invalid_readline_line?(line)
@history << line.chomp
@original_lines += 1
@history_line_count += 1
end
end
# Add a line to the input history, ignoring blank and duplicate lines.
# @param [String] line
# @return [String] The same line that was passed in
def push(line)
return line if line.empty? || invalid_readline_line?(line)
begin
last_line = @history[-1]
rescue IndexError
last_line = nil
end
return line if line == last_line
@history << line
@history_line_count += 1
@saver.call(line) if !should_ignore?(line) && Pry.config.history_save
line
end
alias << push
# Clear this session's history. This won't affect the contents of the
# history file.
def clear
@history.clear
@history_line_count = 0
@original_lines = 0
end
# @return [Fixnum] The number of lines in history from just this session.
def session_line_count
@history_line_count - @original_lines
end
# Return an Array containing all stored history.
# @return [Array<String>] An Array containing all lines of history loaded
# or entered by the user in the current session.
def to_a
@history.to_a
end
# Filter the history with the histignore options
# @return [Array<String>] An array containing all the lines that are not
# included in the histignore.
def filter(history)
history.select { |l| l unless should_ignore?(l) }
end
private
# Check if the line match any option in the histignore
# [Pry.config.history_ignorelist]
# @return [Boolean] a boolean that notifies if the line was found in the
# histignore array.
def should_ignore?(line)
hist_ignore = Pry.config.history_ignorelist
return false if hist_ignore.nil? || hist_ignore.empty?
hist_ignore.any? { |p| line.to_s.match(p) }
end
# The default loader. Yields lines from `Pry.config.history_file`.
def read_from_file
path = history_file_path
File.foreach(path) { |line| yield(line) } if File.exist?(path)
rescue SystemCallError => error
warn "Unable to read history file: #{error.message}"
end
# The default saver. Appends the given line to `Pry.config.history_file`.
def save_to_file(line)
history_file.puts line if history_file
end
# The history file, opened for appending.
def history_file
if defined?(@history_file)
@history_file
else
unless File.exist?(history_file_path)
FileUtils.mkdir_p(File.dirname(history_file_path))
end
@history_file = File.open(history_file_path, 'a', 0o600).tap do |file|
file.sync = true
end
end
rescue SystemCallError => error
warn "Unable to write history file: #{error.message}"
@history_file = false
end
def history_file_path
File.expand_path(@file_path || Pry.config.history_file)
end
def invalid_readline_line?(line)
# `Readline::HISTORY << line` raises an `ArgumentError` if `line`
# includes a null byte
line.include?("\0")
end
end
end

Some files were not shown because too many files have changed in this diff Show More