This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user