1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00

Move all commands into individual files

This commit is contained in:
Ryan Fitzgerald 2012-08-11 17:22:29 -07:00
parent 997cd975b4
commit 25cbe43137
59 changed files with 2598 additions and 2689 deletions

View file

@ -169,6 +169,7 @@ require "coderay"
require "optparse"
require "slop"
require "rbconfig"
require 'tempfile'
begin
require 'readline'

View file

@ -1,29 +1,10 @@
require "pry/default_commands/misc"
require "pry/default_commands/help"
require "pry/default_commands/gems"
require "pry/default_commands/context"
require "pry/default_commands/commands"
require "pry/default_commands/input_and_output"
require "pry/default_commands/introspection"
require "pry/default_commands/editing"
require "pry/default_commands/navigating_pry"
require "pry/default_commands/easter_eggs"
# Default commands used by Pry.
Pry::Commands = Pry::CommandSet.new
require "pry/extended_commands/experimental"
class Pry
# Default commands used by Pry.
Commands = Pry::CommandSet.new do
import DefaultCommands::Misc
import DefaultCommands::Help
import DefaultCommands::Gems
import DefaultCommands::Context
import DefaultCommands::NavigatingPry
import DefaultCommands::Editing
import DefaultCommands::InputAndOutput
import DefaultCommands::Introspection
import DefaultCommands::EasterEggs
import DefaultCommands::Commands
end
Dir[File.expand_path('../default_commands/*.rb', __FILE__)].each do |file|
require file
end
Dir[File.expand_path('../extended_commands/*.rb', __FILE__)].each do |file|
require file
end

View file

@ -0,0 +1,43 @@
class Pry
Pry::Commands.create_command(/amend-line(?: (-?\d+)(?:\.\.(-?\d+))?)?/) do
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 in `amend-line 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.
e.g amend-line 1 puts 'hello world! # replace line 1'
e.g amend-line 1..4 ! # delete lines 1..4
e.g amend-line 3 >puts 'goodbye' # insert before line 3
e.g amend-line puts 'hello again' # no line number modifies immediately preceding line
BANNER
def process
start_line_number, end_line_number, replacement_line = *args
if eval_string.empty?
raise CommandError, "No input to amend."
end
replacement_line = "" if !replacement_line
input_array = eval_string.each_line.to_a
end_line_number = start_line_number.to_i if !end_line_number
line_range = start_line_number ? (one_index_number(start_line_number.to_i)..one_index_number(end_line_number.to_i)) : input_array.size - 1
# delete selected lines if replacement line is '!'
if arg_string == "!"
input_array.slice!(line_range)
elsif arg_string.start_with?(">")
insert_slot = Array(line_range).first
input_array.insert(insert_slot, arg_string[1..-1] + "\n")
else
input_array[line_range] = arg_string + "\n"
end
eval_string.replace input_array.join
run "show-input"
end
end
end

View file

@ -0,0 +1,8 @@
class Pry
Pry::Commands.create_command "!", "Clear the input buffer. Useful if the parsing process goes wrong and you get stuck in the read loop.", :use_prefix => false do
def process
output.puts "Input buffer cleared!"
eval_string.replace("")
end
end
end

View file

@ -0,0 +1,5 @@
class Pry
Pry::Commands.command "!pry", "Start a Pry session on current self; this even works mid multi-line expression." do
target.pry
end
end

View file

@ -0,0 +1,170 @@
class Pry
Pry::Commands.create_command "cat", "Show code from a file, Pry's input buffer, or the last exception." do
banner <<-USAGE
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.
USAGE
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
opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
end
def process
handler = case
when opts.present?(:ex)
method :process_ex
when opts.present?(:in)
method :process_in
else
method :process_file
end
output = handler.call do |code|
code.code_type = opts[:type] || :ruby
code.between(opts[:start] || 1, opts[:end] || -1).
with_line_numbers(opts.present?(:'line-numbers') || opts.present?(:ex))
end
render_output(output, opts)
end
def process_ex
window_size = Pry.config.default_window_size || 5
ex = _pry_.last_exception
raise CommandError, "No exception found." unless ex
if opts[:ex].nil?
bt_index = ex.bt_index
ex.inc_bt_index
else
bt_index = opts[:ex]
ex.bt_index = bt_index
ex.inc_bt_index
end
ex_file, ex_line = ex.bt_source_location_for(bt_index)
raise CommandError, "The given backtrace level is out of bounds." unless ex_file
if RbxPath.is_core_path?(ex_file)
ex_file = RbxPath.convert_path_to_full(ex_file)
end
set_file_and_dir_locals(ex_file)
start_line = ex_line - window_size
start_line = 1 if start_line < 1
end_line = ex_line + window_size
header = unindent <<-HEADER
#{text.bold 'Exception:'} #{ex.class}: #{ex.message}
--
#{text.bold('From:')} #{ex_file} @ line #{ex_line} @ #{text.bold("level: #{bt_index}")} of backtrace (of #{ex.backtrace.size - 1}).
HEADER
code = yield(Pry::Code.from_file(ex_file).
between(start_line, end_line).
with_marker(ex_line))
"#{header}#{code}"
end
def process_in
normalized_range = absolute_index_range(opts[:i], _pry_.input_array.length)
input_items = _pry_.input_array[normalized_range] || []
zipped_items = normalized_range.zip(input_items).reject { |_, s| s.nil? || s == "" }
unless zipped_items.length > 0
raise CommandError, "No expressions found."
end
if zipped_items.length > 1
contents = ""
zipped_items.each do |i, s|
contents << "#{text.bold(i.to_s)}:\n"
contents << yield(Pry::Code(s).with_indentation(2)).to_s
end
else
contents = yield(Pry::Code(zipped_items.first.last))
end
contents
end
def process_file
file_name = args.shift
unless file_name
raise CommandError, "Must provide a filename, --in, or --ex."
end
file_name, line_num = file_name.split(':')
file_name = File.expand_path(file_name)
set_file_and_dir_locals(file_name)
code = yield(Pry::Code.from_file(file_name))
code.code_type = opts[:type] || detect_code_type_from_file(file_name)
if line_num
code = code.around(line_num.to_i,
Pry.config.default_window_size || 7)
end
code
end
def detect_code_type_from_file(file_name)
name, ext = File.basename(file_name).split('.', 2)
if ext
case ext
when "py"
:python
when "rb", "gemspec", "rakefile", "ru"
:ruby
when "js"
return :javascript
when "yml", "prytheme"
:yaml
when "groovy"
:groovy
when "c"
:c
when "cpp"
:cpp
when "java"
:java
else
:text
end
else
case name
when "Rakefile", "Gemfile"
:ruby
else
:text
end
end
end
end
end

View file

@ -1,81 +1,76 @@
class Pry
module DefaultCommands
Cd = Pry::CommandSet.new do
create_command "cd" do
group "Context"
description "Move into a new context (object or scope)."
Pry::Commands.create_command "cd" do
group "Context"
description "Move into a new context (object or scope)."
banner <<-BANNER
Usage: cd [OPTIONS] [--help]
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.
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.
e.g: `cd @x`
e.g: `cd ..`
e.g: `cd /`
e.g: `cd -`
e.g: `cd @x`
e.g: `cd ..`
e.g: `cd /`
e.g: `cd -`
https://github.com/pry/pry/wiki/State-navigation#wiki-Changing_scope
BANNER
https://github.com/pry/pry/wiki/State-navigation#wiki-Changing_scope
BANNER
def process
# Extract command arguments. Delete blank arguments like " ", but
# don't delete empty strings like "".
path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ }
stack = _pry_.binding_stack.dup
old_stack = state.old_stack || []
# Special case when we only get a single "/", return to root.
if path.empty?
state.old_stack = stack.dup unless old_stack.empty?
stack = [stack.first]
end
path.each_with_index do |context, i|
begin
case context.chomp
when ""
state.old_stack = stack.dup
stack = [stack.first]
when "::"
state.old_stack = stack.dup
stack.push(TOPLEVEL_BINDING)
when "."
next
when ".."
unless stack.size == 1
# Don't rewrite old_stack if we're in complex expression
# (e.g.: `cd 1/2/3/../4).
state.old_stack = stack.dup if path.first == ".."
stack.pop
end
when "-"
unless old_stack.empty?
# Interchange current stack and old stack with each other.
stack, state.old_stack = state.old_stack, stack
end
else
state.old_stack = stack.dup if i == 0
stack.push(Pry.binding_for(stack.last.eval(context)))
end
rescue RescuableException => e
# Restore old stack to its initial values.
state.old_stack = old_stack
output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}"
output.puts e.inspect
return
end
end
_pry_.binding_stack = stack
end
def process
# Extract command arguments. Delete blank arguments like " ", but
# don't delete empty strings like "".
path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ }
stack = _pry_.binding_stack.dup
old_stack = state.old_stack || []
# Special case when we only get a single "/", return to root.
if path.empty?
state.old_stack = stack.dup unless old_stack.empty?
stack = [stack.first]
end
path.each_with_index do |context, i|
begin
case context.chomp
when ""
state.old_stack = stack.dup
stack = [stack.first]
when "::"
state.old_stack = stack.dup
stack.push(TOPLEVEL_BINDING)
when "."
next
when ".."
unless stack.size == 1
# Don't rewrite old_stack if we're in complex expression
# (e.g.: `cd 1/2/3/../4).
state.old_stack = stack.dup if path.first == ".."
stack.pop
end
when "-"
unless old_stack.empty?
# Interchange current stack and old stack with each other.
stack, state.old_stack = state.old_stack, stack
end
else
state.old_stack = stack.dup if i == 0
stack.push(Pry.binding_for(stack.last.eval(context)))
end
rescue RescuableException => e
# Restore old stack to its initial values.
state.old_stack = old_stack
output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}"
output.puts e.inspect
return
end
end
_pry_.binding_stack = stack
end
end
end

View file

@ -1,62 +0,0 @@
class Pry
module DefaultCommands
Commands = Pry::CommandSet.new do
create_command "import-set", "Import a command set" do
group "Commands"
def process(command_set_name)
raise CommandError, "Provide a command set name" if command_set.nil?
set = target.eval(arg_string)
_pry_.commands.import set
end
end
create_command "install-command", "Install a disabled command." do |name|
group 'Commands'
banner <<-BANNER
Usage: install-command COMMAND
Installs the gems necessary to run the given COMMAND. You will generally not
need to run this unless told to by an error message.
BANNER
def process(name)
require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller
command = find_command(name)
if command_dependencies_met?(command.options)
output.puts "Dependencies for #{command.name} are met. Nothing to do."
return
end
output.puts "Attempting to install `#{name}` command..."
gems_to_install = Array(command.options[:requires_gem])
gems_to_install.each do |g|
next if gem_installed?(g)
output.puts "Installing `#{g}` gem..."
begin
Gem::DependencyInstaller.new.install(g)
rescue Gem::GemNotFoundException
raise CommandError, "Required Gem: `#{g}` not found. Aborting command installation."
end
end
Gem.refresh
gems_to_install.each do |g|
begin
require g
rescue LoadError
raise CommandError, "Required Gem: `#{g}` installed but not found?!. Aborting command installation."
end
end
output.puts "Installation of `#{name}` successful! Type `help #{name}` for information"
end
end
end
end
end

View file

@ -1,98 +0,0 @@
require "pry/default_commands/ls"
require "pry/default_commands/cd"
require "pry/default_commands/find_method"
require "pry/default_commands/whereami"
class Pry
module DefaultCommands
Context = Pry::CommandSet.new do
import Ls
import Cd
import FindMethod
import Whereami
create_command "pry-backtrace", "Show the backtrace for the Pry session." do
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)
e.g: pry-backtrace
BANNER
def process
output.puts "\n#{text.bold('Backtrace:')}\n--\n"
stagger_output _pry_.backtrace.join("\n")
end
end
command "reset", "Reset the REPL to a clean state." do
output.puts "Pry reset."
exec "pry"
end
create_command(/wtf([?!]*)/, "Show the backtrace of the most recent exception") do
options :listing => 'wtf?'
banner <<-BANNER
Show's 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:
e.g.
pry(main)> wtf?
pry(main)> wtf?!???!?!?
To see the entire backtrace, pass the -v/--verbose flag:
e.g.
pry(main)> wtf -v
BANNER
def options(opt)
opt.on(:v, :verbose, "Show the full backtrace.")
end
def process
raise Pry::CommandError, "No most-recent exception" unless _pry_.last_exception
output.puts "#{text.bold('Exception:')} #{_pry_.last_exception.class}: #{_pry_.last_exception}\n--"
if opts.verbose?
output.puts Code.new(_pry_.last_exception.backtrace, 0, :text).with_line_numbers.to_s
else
output.puts Code.new(_pry_.last_exception.backtrace.first([captures[0].size, 0.5].max * 10), 0, :text).with_line_numbers.to_s
end
end
end
# N.B. using a regular expresion here so that "raise-up 'foo'" does the right thing.
create_command(/raise-up(!?\b.*)/, :listing => 'raise-up') do
description "Raise an exception out of the current pry instance."
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_).
e.g. `raise-up "get-me-out-of-here"` is equivalent to:
`raise "get-me-out-of-here"
raise-up`
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.
BANNER
def process
return stagger_output help if captures[0] =~ /(-h|--help)\b/
# Handle 'raise-up', 'raise-up "foo"', 'raise-up RuntimeError, 'farble' in a rubyesque manner
target.eval("_pry_.raise_up#{captures[0]}")
end
end
end
end
end

View file

@ -1,95 +1,91 @@
class Pry
module DefaultCommands
Pry::Commands.instance_eval do
command "nyan-cat", "", :requires_gem => ["nyancat"] do
run ".nyancat"
end
EasterEggs = Pry::CommandSet.new do
command(/!s\/(.*?)\/(.*?)/, "") do |source, dest|
eval_string.gsub!(/#{source}/) { dest }
run "show-input"
end
command "nyan-cat", "", :requires_gem => ["nyancat"] do
run ".nyancat"
command "get-naked", "" do
text = %{
--
We dont have to take our clothes off to have a good time.
We could dance & party all night And drink some cherry wine.
-- Jermaine Stewart }
output.puts text
text
end
command "east-coker", "" do
text = %{
--
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 text
text
end
command "cohen-poem", "" do
text = %{
--
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 text
text
end
command "test-ansi", "" do
prev_color = Pry.color
Pry.color = true
picture = unindent <<-'EOS'.gsub(/[[:alpha:]!]/) { |s| text.red(s) }
____ _______________________
/ \ | A W G |
/ O O \ | N I O N ! |
| | | S S R I ! |
\ \__/ / __| I K ! |
\____/ \________________________|
EOS
if windows_ansi?
move_up = proc { |n| "\e[#{n}F" }
else
move_up = proc { |n| "\e[#{n}A\e[0G" }
end
command(/!s\/(.*?)\/(.*?)/, "") do |source, dest|
eval_string.gsub!(/#{source}/) { dest }
run "show-input"
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"
command "get-naked", "" do
text = %{
--
We dont have to take our clothes off to have a good time.
We could dance & party all night And drink some cherry wine.
-- Jermaine Stewart }
output.puts text
text
end
command "east-coker", "" do
text = %{
--
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 text
text
end
command "cohen-poem", "" do
text = %{
--
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 text
text
end
command "test-ansi", "" do
prev_color = Pry.color
Pry.color = true
picture = unindent <<-'EOS'.gsub(/[[:alpha:]!]/) { |s| text.red(s) }
____ _______________________
/ \ | A W G |
/ O O \ | N I O N ! |
| | | S S R I ! |
\ \__/ / __| I K ! |
\____/ \________________________|
EOS
if windows_ansi?
move_up = proc { |n| "\e[#{n}F" }
else
move_up = 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.color = prev_color
end
Pry.color = prev_color
end
end
end

View file

@ -0,0 +1,137 @@
class Pry
Pry::Commands.create_command "edit" do
description "Invoke the default editor on a file."
banner <<-BANNER
Usage: edit [--no-reload|--reload] [--line LINE] [--temp|--ex|FILE[:LINE]|--in N]
Open a text editor. When no FILE is given, edits the pry input buffer.
Ensure Pry.config.editor is set to your editor of choice.
e.g: `edit sample.rb`
e.g: `edit sample.rb --line 105`
e.g: `edit --ex`
https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_command
BANNER
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 code"
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)"
end
def process
if [opts.present?(:ex), opts.present?(:temp), opts.present?(:in), !args.empty?].count(true) > 1
raise CommandError, "Only one of --ex, --temp, --in and FILE may be specified."
end
if !opts.present?(:ex) && !opts.present?(:current) && args.empty?
# edit of local code, eval'd within pry.
process_local_edit
else
# edit of remote code, eval'd at top-level
process_remote_edit
end
end
def process_i
case opts[:i]
when Range
(_pry_.input_array[opts[:i]] || []).join
when Fixnum
_pry_.input_array[opts[:i]] || ""
else
return output.puts "Not a valid range: #{opts[:i]}"
end
end
def process_local_edit
content = case
when opts.present?(:temp)
""
when opts.present?(:in)
process_i
when eval_string.strip != ""
eval_string
else
_pry_.input_array.reverse_each.find{ |x| x && x.strip != "" } || ""
end
line = content.lines.count
temp_file do |f|
f.puts(content)
f.flush
reload = !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload
f.close(false)
invoke_editor(f.path, line, reload)
if reload
silence_warnings do
eval_string.replace(File.read(f.path))
end
end
end
end
def process_remote_edit
if opts.present?(:ex)
if _pry_.last_exception.nil?
raise CommandError, "No exception found."
end
ex = _pry_.last_exception
bt_index = opts[:ex].to_i
ex_file, ex_line = ex.bt_source_location_for(bt_index)
if ex_file && RbxPath.is_core_path?(ex_file)
file_name = RbxPath.convert_path_to_full(ex_file)
else
file_name = ex_file
end
line = ex_line
if file_name.nil?
raise CommandError, "Exception has no associated file."
end
if Pry.eval_path == file_name
raise CommandError, "Cannot edit exceptions raised in REPL."
end
elsif opts.present?(:current)
file_name = target.eval("__FILE__")
line = target.eval("__LINE__")
else
# break up into file:line
file_name = File.expand_path(args.first)
line = file_name.sub!(/:(\d+)$/, "") ? $1.to_i : 1
end
if not_a_real_file?(file_name)
raise CommandError, "#{file_name} is not a valid file name, cannot edit!"
end
line = opts[:l].to_i if opts.present?(:line)
reload = opts.present?(:reload) || ((opts.present?(:ex) || file_name.end_with?(".rb")) && !opts.present?(:'no-reload')) && !Pry.config.disable_auto_reload
# Sanitize blanks.
sanitized_file_name = Shellwords.escape(file_name)
invoke_editor(sanitized_file_name, line, reload)
set_file_and_dir_locals(sanitized_file_name)
if reload
silence_warnings do
TOPLEVEL_BINDING.eval(File.read(file_name), file_name)
end
end
end
end
end

View file

@ -0,0 +1,179 @@
class Pry
Pry::Commands.create_command "edit-method" do
description "Edit the source code for a method."
banner <<-BANNER
Usage: edit-method [OPTIONS] [METH]
Edit the method METH in an editor.
Ensure Pry.config.editor is set to your editor of choice.
e.g: `edit-method hello_method`
e.g: `edit-method Pry#rep`
e.g: `edit-method`
https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_method
BANNER
command_options :shellwords => false
def options(opt)
method_options(opt)
opt.on :n, "no-reload", "Do not automatically reload the method's file after editing."
opt.on "no-jump", "Do not fast forward editor to first line of method."
opt.on :p, :patch, "Instead of editing the method's file, try to edit in a tempfile and apply as a monkey patch."
end
def process
if !Pry.config.editor
raise CommandError, "No editor set!\nEnsure that #{text.bold("Pry.config.editor")} is set to your editor of choice."
end
begin
@method = method_object
rescue MethodNotFound => err
end
if opts.present?(:patch) || (@method && @method.dynamically_defined?)
if err
raise err # can't patch a non-method
end
process_patch
else
if err && !File.exist?(target.eval('__FILE__'))
raise err # can't edit a non-file
end
process_file
end
end
def process_patch
lines = @method.source.lines.to_a
lines[0] = definition_line_for_owner(lines[0])
temp_file do |f|
f.puts lines
f.flush
f.close(false)
invoke_editor(f.path, 0, true)
source = wrap_for_nesting(wrap_for_owner(File.read(f.path)))
if @method.alias?
with_method_transaction(original_name, @method.owner) do
Pry.new(:input => StringIO.new(source)).rep(TOPLEVEL_BINDING)
Pry.binding_for(@method.owner).eval("alias #{@method.name} #{original_name}")
end
else
Pry.new(:input => StringIO.new(source)).rep(TOPLEVEL_BINDING)
end
end
end
def process_file
file, line = extract_file_and_line
reload = !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload
invoke_editor(file, opts["no-jump"] ? 0 : line, reload)
silence_warnings do
load file if reload
end
end
protected
def extract_file_and_line
if @method
if @method.source_type == :c
raise CommandError, "Can't edit a C method."
else
[@method.source_file, @method.source_line]
end
else
[target.eval('__FILE__'), target.eval('__LINE__')]
end
end
# Run some code ensuring that at the end target#meth_name will not have changed.
#
# When we're redefining aliased methods we will overwrite the method at the
# unaliased name (so that super continues to work). By wrapping that code in a
# transation we make that not happen, which means that alias_method_chains, etc.
# continue to work.
#
# @param [String] meth_name The method name before aliasing
# @param [Module] target The owner of the method
def with_method_transaction(meth_name, target)
target = Pry.binding_for(target)
temp_name = "__pry_#{meth_name}__"
target.eval("alias #{temp_name} #{meth_name}")
yield
target.eval("alias #{meth_name} #{temp_name}")
ensure
target.eval("undef #{temp_name}") rescue nil
end
# The original name of the method, if it's not present raise an error telling
# the user why we don't work.
#
def original_name
@method.original_name or raise CommandError, "Pry can only patch methods created with the `def` keyword."
end
# Update the definition line so that it can be eval'd directly on the Method's
# owner instead of from the original context.
#
# In particular this takes `def self.foo` and turns it into `def foo` so that we
# don't end up creating the method on the singleton class of the singleton class
# by accident.
#
# This is necessarily done by String manipulation because we can't find out what
# syntax is needed for the argument list by ruby-level introspection.
#
# @param String The original definition line. e.g. def self.foo(bar, baz=1)
# @return String The new definition line. e.g. def foo(bar, baz=1)
def definition_line_for_owner(line)
if line =~ /^def (?:.*?\.)?#{Regexp.escape(original_name)}(?=[\(\s;]|$)/
"def #{original_name}#{$'}"
else
raise CommandError, "Could not find original `def #{original_name}` line to patch."
end
end
# Update the source code so that when it has the right owner when eval'd.
#
# This (combined with definition_line_for_owner) is backup for the case that
# wrap_for_nesting fails, to ensure that the method will stil be defined in
# the correct place.
#
# @param [String] source The source to wrap
# @return [String]
def wrap_for_owner(source)
Thread.current[:__pry_owner__] = @method.owner
source = "Thread.current[:__pry_owner__].class_eval do\n#{source}\nend"
end
# Update the new source code to have the correct Module.nesting.
#
# This method uses syntactic analysis of the original source file to determine
# the new nesting, so that we can tell the difference between:
#
# class A; def self.b; end; end
# class << A; def b; end; end
#
# The resulting code should be evaluated in the TOPLEVEL_BINDING.
#
# @param [String] source The source to wrap.
# @return [String]
def wrap_for_nesting(source)
nesting = Pry::Code.from_file(@method.source_file).nesting_at(@method.source_line)
(nesting + [source] + nesting.map{ "end" } + [""]).join("\n")
rescue Pry::Indent::UnparseableNestingError => e
source
end
end
end

View file

@ -1,463 +0,0 @@
require 'tempfile'
require 'shellwords'
require 'pry/default_commands/hist'
class Pry
module DefaultCommands
Editing = Pry::CommandSet.new do
import Hist
create_command "!", "Clear the input buffer. Useful if the parsing process goes wrong and you get stuck in the read loop.", :use_prefix => false do
def process
output.puts "Input buffer cleared!"
eval_string.replace("")
end
end
create_command "show-input", "Show the contents of the input buffer for the current multi-line expression." do
def process
output.puts Code.new(eval_string).with_line_numbers
end
end
create_command "edit" do
description "Invoke the default editor on a file."
banner <<-BANNER
Usage: edit [--no-reload|--reload] [--line LINE] [--temp|--ex|FILE[:LINE]|--in N]
Open a text editor. When no FILE is given, edits the pry input buffer.
Ensure Pry.config.editor is set to your editor of choice.
e.g: `edit sample.rb`
e.g: `edit sample.rb --line 105`
e.g: `edit --ex`
https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_command
BANNER
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 code"
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)"
end
def process
if [opts.present?(:ex), opts.present?(:temp), opts.present?(:in), !args.empty?].count(true) > 1
raise CommandError, "Only one of --ex, --temp, --in and FILE may be specified."
end
if !opts.present?(:ex) && !opts.present?(:current) && args.empty?
# edit of local code, eval'd within pry.
process_local_edit
else
# edit of remote code, eval'd at top-level
process_remote_edit
end
end
def process_i
case opts[:i]
when Range
(_pry_.input_array[opts[:i]] || []).join
when Fixnum
_pry_.input_array[opts[:i]] || ""
else
return output.puts "Not a valid range: #{opts[:i]}"
end
end
def process_local_edit
content = case
when opts.present?(:temp)
""
when opts.present?(:in)
process_i
when eval_string.strip != ""
eval_string
else
_pry_.input_array.reverse_each.find{ |x| x && x.strip != "" } || ""
end
line = content.lines.count
temp_file do |f|
f.puts(content)
f.flush
reload = !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload
f.close(false)
invoke_editor(f.path, line, reload)
if reload
silence_warnings do
eval_string.replace(File.read(f.path))
end
end
end
end
def process_remote_edit
if opts.present?(:ex)
if _pry_.last_exception.nil?
raise CommandError, "No exception found."
end
ex = _pry_.last_exception
bt_index = opts[:ex].to_i
ex_file, ex_line = ex.bt_source_location_for(bt_index)
if ex_file && RbxPath.is_core_path?(ex_file)
file_name = RbxPath.convert_path_to_full(ex_file)
else
file_name = ex_file
end
line = ex_line
if file_name.nil?
raise CommandError, "Exception has no associated file."
end
if Pry.eval_path == file_name
raise CommandError, "Cannot edit exceptions raised in REPL."
end
elsif opts.present?(:current)
file_name = target.eval("__FILE__")
line = target.eval("__LINE__")
else
# break up into file:line
file_name = File.expand_path(args.first)
line = file_name.sub!(/:(\d+)$/, "") ? $1.to_i : 1
end
if not_a_real_file?(file_name)
raise CommandError, "#{file_name} is not a valid file name, cannot edit!"
end
line = opts[:l].to_i if opts.present?(:line)
reload = opts.present?(:reload) || ((opts.present?(:ex) || file_name.end_with?(".rb")) && !opts.present?(:'no-reload')) && !Pry.config.disable_auto_reload
# Sanitize blanks.
sanitized_file_name = Shellwords.escape(file_name)
invoke_editor(sanitized_file_name, line, reload)
set_file_and_dir_locals(sanitized_file_name)
if reload
silence_warnings do
TOPLEVEL_BINDING.eval(File.read(file_name), file_name)
end
end
end
end
create_command "edit-method" do
description "Edit the source code for a method."
banner <<-BANNER
Usage: edit-method [OPTIONS] [METH]
Edit the method METH in an editor.
Ensure Pry.config.editor is set to your editor of choice.
e.g: `edit-method hello_method`
e.g: `edit-method Pry#rep`
e.g: `edit-method`
https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_method
BANNER
command_options :shellwords => false
def options(opt)
method_options(opt)
opt.on :n, "no-reload", "Do not automatically reload the method's file after editing."
opt.on "no-jump", "Do not fast forward editor to first line of method."
opt.on :p, :patch, "Instead of editing the method's file, try to edit in a tempfile and apply as a monkey patch."
end
def process
if !Pry.config.editor
raise CommandError, "No editor set!\nEnsure that #{text.bold("Pry.config.editor")} is set to your editor of choice."
end
begin
@method = method_object
rescue MethodNotFound => err
end
if opts.present?(:patch) || (@method && @method.dynamically_defined?)
if err
raise err # can't patch a non-method
end
process_patch
else
if err && !File.exist?(target.eval('__FILE__'))
raise err # can't edit a non-file
end
process_file
end
end
def process_patch
lines = @method.source.lines.to_a
lines[0] = definition_line_for_owner(lines[0])
temp_file do |f|
f.puts lines
f.flush
f.close(false)
invoke_editor(f.path, 0, true)
source = wrap_for_nesting(wrap_for_owner(File.read(f.path)))
if @method.alias?
with_method_transaction(original_name, @method.owner) do
Pry.new(:input => StringIO.new(source)).rep(TOPLEVEL_BINDING)
Pry.binding_for(@method.owner).eval("alias #{@method.name} #{original_name}")
end
else
Pry.new(:input => StringIO.new(source)).rep(TOPLEVEL_BINDING)
end
end
end
def process_file
file, line = extract_file_and_line
reload = !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload
invoke_editor(file, opts["no-jump"] ? 0 : line, reload)
silence_warnings do
load file if reload
end
end
protected
def extract_file_and_line
if @method
if @method.source_type == :c
raise CommandError, "Can't edit a C method."
else
[@method.source_file, @method.source_line]
end
else
[target.eval('__FILE__'), target.eval('__LINE__')]
end
end
# Run some code ensuring that at the end target#meth_name will not have changed.
#
# When we're redefining aliased methods we will overwrite the method at the
# unaliased name (so that super continues to work). By wrapping that code in a
# transation we make that not happen, which means that alias_method_chains, etc.
# continue to work.
#
# @param [String] meth_name The method name before aliasing
# @param [Module] target The owner of the method
def with_method_transaction(meth_name, target)
target = Pry.binding_for(target)
temp_name = "__pry_#{meth_name}__"
target.eval("alias #{temp_name} #{meth_name}")
yield
target.eval("alias #{meth_name} #{temp_name}")
ensure
target.eval("undef #{temp_name}") rescue nil
end
# The original name of the method, if it's not present raise an error telling
# the user why we don't work.
#
def original_name
@method.original_name or raise CommandError, "Pry can only patch methods created with the `def` keyword."
end
# Update the definition line so that it can be eval'd directly on the Method's
# owner instead of from the original context.
#
# In particular this takes `def self.foo` and turns it into `def foo` so that we
# don't end up creating the method on the singleton class of the singleton class
# by accident.
#
# This is necessarily done by String manipulation because we can't find out what
# syntax is needed for the argument list by ruby-level introspection.
#
# @param String The original definition line. e.g. def self.foo(bar, baz=1)
# @return String The new definition line. e.g. def foo(bar, baz=1)
def definition_line_for_owner(line)
if line =~ /^def (?:.*?\.)?#{Regexp.escape(original_name)}(?=[\(\s;]|$)/
"def #{original_name}#{$'}"
else
raise CommandError, "Could not find original `def #{original_name}` line to patch."
end
end
# Update the source code so that when it has the right owner when eval'd.
#
# This (combined with definition_line_for_owner) is backup for the case that
# wrap_for_nesting fails, to ensure that the method will stil be defined in
# the correct place.
#
# @param [String] source The source to wrap
# @return [String]
def wrap_for_owner(source)
Thread.current[:__pry_owner__] = @method.owner
source = "Thread.current[:__pry_owner__].class_eval do\n#{source}\nend"
end
# Update the new source code to have the correct Module.nesting.
#
# This method uses syntactic analysis of the original source file to determine
# the new nesting, so that we can tell the difference between:
#
# class A; def self.b; end; end
# class << A; def b; end; end
#
# The resulting code should be evaluated in the TOPLEVEL_BINDING.
#
# @param [String] source The source to wrap.
# @return [String]
def wrap_for_nesting(source)
nesting = Pry::Code.from_file(@method.source_file).nesting_at(@method.source_line)
(nesting + [source] + nesting.map{ "end" } + [""]).join("\n")
rescue Pry::Indent::UnparseableNestingError => e
source
end
end
create_command(/amend-line(?: (-?\d+)(?:\.\.(-?\d+))?)?/) do
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 in `amend-line 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.
e.g amend-line 1 puts 'hello world! # replace line 1'
e.g amend-line 1..4 ! # delete lines 1..4
e.g amend-line 3 >puts 'goodbye' # insert before line 3
e.g amend-line puts 'hello again' # no line number modifies immediately preceding line
BANNER
def process
start_line_number, end_line_number, replacement_line = *args
if eval_string.empty?
raise CommandError, "No input to amend."
end
replacement_line = "" if !replacement_line
input_array = eval_string.each_line.to_a
end_line_number = start_line_number.to_i if !end_line_number
line_range = start_line_number ? (one_index_number(start_line_number.to_i)..one_index_number(end_line_number.to_i)) : input_array.size - 1
# delete selected lines if replacement line is '!'
if arg_string == "!"
input_array.slice!(line_range)
elsif arg_string.start_with?(">")
insert_slot = Array(line_range).first
input_array.insert(insert_slot, arg_string[1..-1] + "\n")
else
input_array[line_range] = arg_string + "\n"
end
eval_string.replace input_array.join
run "show-input"
end
end
create_command "play" do
include Helpers::DocumentationHelpers
description "Play back a string variable or a method or a 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. Default action (no
options) is to play the provided string variable
e.g: `play -i 20 --lines 1..3`
e.g: `play -m Pry#repl --lines 1..-1`
e.g: `play -f Rakefile --lines 5`
https://github.com/pry/pry/wiki/User-Input#wiki-Play
BANNER
attr_accessor :content
def setup
self.content = ""
end
def options(opt)
opt.on :m, :method, "Play a method's source.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
self.content << meth.source
end
opt.on :d, :doc, "Play a method's documentation.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
text.no_color do
self.content << process_comment_markup(meth.doc)
end
end
opt.on :c, :command, "Play a command's source.", :argument => true do |command_name|
command = find_command(command_name)
block = Pry::Method.new(command.block)
self.content << block.source
end
opt.on :f, :file, "Play a file.", :argument => true do |file|
self.content << File.read(File.expand_path(file))
end
opt.on :l, :lines, "Only play a subset of lines.", :optional_argument => true, :as => Range, :default => 1..-1
opt.on :i, :in, "Play entries from Pry's input expression history. Takes an index or range. Note this can only replay pure Ruby code, not Pry commands.", :optional_argument => true,
:as => Range, :default => -5..-1 do |range|
input_expressions = _pry_.input_array[range] || []
Array(input_expressions).each { |v| self.content << v }
end
opt.on :o, "open", 'When used with the -m switch, it plays the entire method except the last line, leaving the method definition "open". `amend-line` can then be used to modify the method.'
end
def process
perform_play
run "show-input" unless Pry::Code.complete_expression?(eval_string)
end
def process_non_opt
args.each do |arg|
begin
self.content << target.eval(arg)
rescue Pry::RescuableException
raise CommandError, "Problem when evaling #{arg}."
end
end
end
def perform_play
process_non_opt
if opts.present?(:lines)
self.content = restrict_to_lines(self.content, opts[:l])
end
if opts.present?(:open)
self.content = restrict_to_lines(self.content, 1..-2)
end
eval_string << self.content
end
end
end
end
end

View file

@ -0,0 +1,41 @@
class Pry
Pry::Commands.create_command "exit" do
description "Pop the previous binding (does NOT exit program). Aliases: quit"
banner <<-BANNER
Usage: exit [OPTIONS] [--help]
Aliases: quit
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.
e.g: `exit "pry this"`
e.g: `exit`
https://github.com/pry/pry/wiki/State-navigation#wiki-Exit_with_value
BANNER
command_options(
:keep_retval => true
)
def process
if _pry_.binding_stack.one?
_pry_.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_.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.alias_command "quit", "exit"
end

View file

@ -0,0 +1,14 @@
class Pry
Pry::Commands.command "exit-all", "End the current Pry session (popping all bindings) and returning to caller. Accepts optional return value. Aliases: !!@" do
# calculate user-given value
exit_value = target.eval(arg_string)
# clear the binding stack
_pry_.binding_stack.clear
# break out of the repl loop
throw(:breakout, exit_value)
end
Pry::Commands.alias_command "!!@", "exit-all"
end

View file

@ -0,0 +1,9 @@
class Pry
Pry::Commands.command "exit-program", "End the current program. Aliases: quit-program, !!!" do
Pry.save_history if Pry.config.history.should_save
Kernel.exit target.eval(arg_string).to_i
end
Pry::Commands.alias_command "quit-program", "exit-program"
Pry::Commands.alias_command "!!!", "exit-program"
end

View file

@ -1,168 +1,163 @@
class Pry
module DefaultCommands
FindMethod = Pry::CommandSet.new do
Pry::Commands.create_command "find-method" do
extend Pry::Helpers::BaseHelpers
create_command "find-method" do
extend Helpers::BaseHelpers
group "Context"
group "Context"
options :requires_gem => "ruby18_source_location" if mri_18?
options :shellwords => false
options :requires_gem => "ruby18_source_location" if mri_18?
options :shellwords => false
description "Recursively search for a method within a Class/Module or the current namespace. find-method [-n | -c] METHOD [NAMESPACE]"
description "Recursively search for a method within a Class/Module or the current namespace. find-method [-n | -c] METHOD [NAMESPACE]"
banner <<-BANNER
Usage: find-method [-n | -c] METHOD [NAMESPACE]
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.
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.
e.g find-method re Pry # find all methods whose name match /re/ inside the Pry namespace. Matches Pry#repl, etc.
e.g find-method -c 'output.puts' Pry # find all methods that contain the code: output.puts inside the Pry namepsace.
BANNER
e.g find-method re Pry # find all methods whose name match /re/ inside the Pry namespace. Matches Pry#repl, etc.
e.g find-method -c 'output.puts' Pry # find all methods that contain the code: output.puts inside the Pry namepsace.
BANNER
def setup
require 'ruby18_source_location' if mri_18?
end
def setup
require 'ruby18_source_location' if mri_18?
def options(opti)
opti.on :n, :name, "Search for a method by name"
opti.on :c, :content, "Search for a method based on content in Regex form"
end
def process
return if args.size < 1
pattern = ::Regexp.new args[0]
if args[1]
klass = target.eval(args[1])
if !klass.is_a?(Module)
klass = klass.class
end
else
klass = (target_self.is_a?(Module)) ? target_self : target_self.class
end
def options(opti)
opti.on :n, :name, "Search for a method by name"
opti.on :c, :content, "Search for a method based on content in Regex form"
matches = if opts.content?
content_search(pattern, klass)
else
name_search(pattern, klass)
end
if matches.empty?
output.puts text.bold("No Methods Matched")
else
print_matches(matches, pattern)
end
end
private
# pretty-print a list of matching methods.
#
# @param Array[Method]
def print_matches(matches, pattern)
grouped = matches.group_by(&:owner)
order = grouped.keys.sort_by{ |x| x.name || x.to_s }
order.each do |klass|
output.puts text.bold(klass.name)
grouped[klass].each do |method|
header = method.name_with_owner
extra = if opts.content?
header += ": "
colorize_code((method.source.split(/\n/).select {|x| x =~ pattern }).join("\n#{' ' * header.length}"))
else
""
end
output.puts header + extra
end
end
end
def process
return if args.size < 1
pattern = ::Regexp.new args[0]
if args[1]
klass = target.eval(args[1])
if !klass.is_a?(Module)
klass = klass.class
end
else
klass = (target_self.is_a?(Module)) ? target_self : target_self.class
end
# Run the given block against every constant in the provided namespace.
#
# @param Module The namespace in which to start the search.
# @param Hash[Module,Boolean] The namespaces we've already visited (private)
# @yieldparam klazz Each class/module in the namespace.
#
def recurse_namespace(klass, done={}, &block)
return if !(Module === klass) || done[klass]
matches = if opts.content?
content_search(pattern, klass)
else
name_search(pattern, klass)
end
done[klass] = true
if matches.empty?
output.puts text.bold("No Methods Matched")
else
print_matches(matches, pattern)
end
yield klass
klass.constants.each do |name|
next if klass.autoload?(name)
begin
const = klass.const_get(name)
rescue RescuableException
# 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
private
# Gather all the methods in a namespace that pass the given block.
#
# @param Module The namespace in which to search.
# @yieldparam Method The method to test
# @yieldreturn Boolean
# @return Array[Method]
#
def search_all_methods(namespace)
done = Hash.new{ |h,k| h[k] = {} }
matches = []
# pretty-print a list of matching methods.
#
# @param Array[Method]
def print_matches(matches, pattern)
grouped = matches.group_by(&:owner)
order = grouped.keys.sort_by{ |x| x.name || x.to_s }
recurse_namespace(namespace) do |klass|
(Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)).each do |method|
next if done[method.owner][method.name]
done[method.owner][method.name] = true
order.each do |klass|
output.puts text.bold(klass.name)
grouped[klass].each do |method|
header = method.name_with_owner
extra = if opts.content?
header += ": "
colorize_code((method.source.split(/\n/).select {|x| x =~ pattern }).join("\n#{' ' * header.length}"))
else
""
end
output.puts header + extra
end
end
matches << method if yield method
end
end
# Run the given block against every constant in the provided namespace.
#
# @param Module The namespace in which to start the search.
# @param Hash[Module,Boolean] The namespaces we've already visited (private)
# @yieldparam klazz Each class/module in the namespace.
#
def recurse_namespace(klass, done={}, &block)
return if !(Module === klass) || done[klass]
matches
end
done[klass] = true
# Search for all methods with a name that matches the given regex
# within a namespace.
#
# @param Regex The regex to search for
# @param Module The namespace to search
# @return Array[Method]
#
def name_search(regex, namespace)
search_all_methods(namespace) do |meth|
meth.name =~ regex
end
end
yield klass
klass.constants.each do |name|
next if klass.autoload?(name)
begin
const = klass.const_get(name)
rescue RescuableException
# 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 The namespace in which to search.
# @yieldparam 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|
(Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)).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 Regex The regex to search for
# @param Module The namespace to search
# @return Array[Method]
#
def name_search(regex, namespace)
search_all_methods(namespace) do |meth|
meth.name =~ regex
end
end
# Search for all methods who's implementation matches the given regex
# within a namespace.
#
# @param Regex The regex to search for
# @param Module The namespace to search
# @return Array[Method]
#
def content_search(regex, namespace)
search_all_methods(namespace) do |meth|
begin
meth.source =~ regex
rescue RescuableException
false
end
end
# Search for all methods who's implementation matches the given regex
# within a namespace.
#
# @param Regex The regex to search for
# @param Module The namespace to search
# @return Array[Method]
#
def content_search(regex, namespace)
search_all_methods(namespace) do |meth|
begin
meth.source =~ regex
rescue RescuableException
false
end
end
end

View file

@ -0,0 +1,20 @@
class Pry
Pry::Commands.create_command "gem-cd", "Change working directory to specified gem's directory.", :argument_required => true do |gem|
banner <<-BANNER
Usage: gem-cd GEM_NAME
Change the current working directory to that in which the given gem is installed.
BANNER
def process(gem)
specs = Gem::Specification.respond_to?(:each) ? Gem::Specification.find_all_by_name(gem) : Gem.source_index.find_name(gem)
spec = specs.sort { |a,b| Gem::Version.new(b.version) <=> Gem::Version.new(a.version) }.first
if spec
Dir.chdir(spec.full_gem_path)
output.puts(Dir.pwd)
else
raise CommandError, "Gem `#{gem}` not found."
end
end
end
end

View file

@ -0,0 +1,28 @@
class Pry
Pry::Commands.create_command "gem-install", "Install a gem and refresh the gem cache.", :argument_required => true do |gem|
banner <<-BANNER
Usage: gem-install GEM_NAME
Installs the given gem and refreshes the gem cache so that you can immediately 'require GEM_FILE'
BANNER
def setup
require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller
end
def process(gem)
begin
destination = File.writable?(Gem.dir) ? Gem.dir : Gem.user_dir
installer = Gem::DependencyInstaller.new :install_dir => destination
installer.install gem
rescue Errno::EACCES
raise CommandError, "Insufficient permissions to install `#{text.green gem}`."
rescue Gem::GemNotFoundException
raise CommandError, "Gem `#{text.green gem}` not found."
else
Gem.refresh
output.puts "Gem `#{text.green gem}` installed."
end
end
end
end

View file

@ -0,0 +1,31 @@
class Pry
Pry::Commands.create_command "gem-list", "List and search installed gems." do |pattern|
banner <<-BANNER
Usage: gem-list [REGEX]
List all installed gems, when a regex is provided, limit the output to those that
match the regex.
BANNER
def process(pattern=nil)
pattern = Regexp.compile(pattern || '')
gems = if Gem::Specification.respond_to?(:each)
Gem::Specification.select{|spec| spec.name =~ pattern }.group_by(&:name)
else
Gem.source_index.gems.values.group_by(&:name).select { |gemname, specs| gemname =~ pattern }
end
gems.each do |gem, specs|
specs.sort! do |a,b|
Gem::Version.new(b.version) <=> Gem::Version.new(a.version)
end
versions = specs.each_with_index.map do |spec, index|
index == 0 ? text.bright_green(spec.version.to_s) : text.green(spec.version.to_s)
end
output.puts "#{text.default gem} (#{versions.join ', '})"
end
end
end
end

View file

@ -1,84 +0,0 @@
class Pry
module DefaultCommands
Gems = Pry::CommandSet.new do
create_command "gem-install", "Install a gem and refresh the gem cache.", :argument_required => true do |gem|
banner <<-BANNER
Usage: gem-install GEM_NAME
Installs the given gem and refreshes the gem cache so that you can immediately 'require GEM_FILE'
BANNER
def setup
require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller
end
def process(gem)
begin
destination = File.writable?(Gem.dir) ? Gem.dir : Gem.user_dir
installer = Gem::DependencyInstaller.new :install_dir => destination
installer.install gem
rescue Errno::EACCES
raise CommandError, "Insufficient permissions to install `#{text.green gem}`."
rescue Gem::GemNotFoundException
raise CommandError, "Gem `#{text.green gem}` not found."
else
Gem.refresh
output.puts "Gem `#{text.green gem}` installed."
end
end
end
create_command "gem-cd", "Change working directory to specified gem's directory.", :argument_required => true do |gem|
banner <<-BANNER
Usage: gem-cd GEM_NAME
Change the current working directory to that in which the given gem is installed.
BANNER
def process(gem)
specs = Gem::Specification.respond_to?(:each) ? Gem::Specification.find_all_by_name(gem) : Gem.source_index.find_name(gem)
spec = specs.sort { |a,b| Gem::Version.new(b.version) <=> Gem::Version.new(a.version) }.first
if spec
Dir.chdir(spec.full_gem_path)
output.puts(Dir.pwd)
else
raise CommandError, "Gem `#{gem}` not found."
end
end
end
create_command "gem-list", "List and search installed gems." do |pattern|
banner <<-BANNER
Usage: gem-list [REGEX]
List all installed gems, when a regex is provided, limit the output to those that
match the regex.
BANNER
def process(pattern=nil)
pattern = Regexp.compile(pattern || '')
gems = if Gem::Specification.respond_to?(:each)
Gem::Specification.select{|spec| spec.name =~ pattern }.group_by(&:name)
else
Gem.source_index.gems.values.group_by(&:name).select { |gemname, specs| gemname =~ pattern }
end
gems.each do |gem, specs|
specs.sort! do |a,b|
Gem::Version.new(b.version) <=> Gem::Version.new(a.version)
end
versions = specs.each_with_index.map do |spec, index|
index == 0 ? text.bright_green(spec.version.to_s) : text.green(spec.version.to_s)
end
output.puts "#{text.default gem} (#{versions.join ', '})"
end
end
end
end
end
end

View file

@ -1,187 +1,183 @@
class Pry
module DefaultCommands
Gist = Pry::CommandSet.new do
create_command "gist", "Gist a method or expression history to GitHub.", :requires_gem => "jist" do
include Pry::Helpers::DocumentationHelpers
Pry::Commands.create_command "gist", "Gist a method or expression history to GitHub.", :requires_gem => "jist" do
include Pry::Helpers::DocumentationHelpers
banner <<-USAGE
Usage: gist [OPTIONS] [METH]
Gist method (doc or source) or input expression to GitHub.
banner <<-USAGE
Usage: gist [OPTIONS] [METH]
Gist method (doc or source) or input expression to GitHub.
If you'd like to permanently associate your gists with your GitHub account run `gist --login`.
If you'd like to permanently associate your gists with your GitHub account run `gist --login`.
e.g: gist -m my_method # gist the method my_method
e.g: gist -d my_method # gist the documentation for my_method
e.g: gist -i 1..10 # gist the input expressions from 1 to 10
e.g: gist -k show-method # gist the command show-method
e.g: gist -c Pry # gist the Pry class
e.g: gist -m hello_world --lines 2..-2 # gist from lines 2 to the second-last of the hello_world method
e.g: gist -m my_method --clip # Copy my_method source to clipboard, do not gist it.
USAGE
e.g: gist -m my_method # gist the method my_method
e.g: gist -d my_method # gist the documentation for my_method
e.g: gist -i 1..10 # gist the input expressions from 1 to 10
e.g: gist -k show-method # gist the command show-method
e.g: gist -c Pry # gist the Pry class
e.g: gist -m hello_world --lines 2..-2 # gist from lines 2 to the second-last of the hello_world method
e.g: gist -m my_method --clip # Copy my_method source to clipboard, do not gist it.
USAGE
command_options :shellwords => false
command_options :shellwords => false
attr_accessor :content
attr_accessor :filename
attr_accessor :content
attr_accessor :filename
def setup
require 'jist'
self.content = ""
self.filename = "a.rb"
def setup
require 'jist'
self.content = ""
self.filename = "a.rb"
end
def options(opt)
opt.on :login, "Authenticate the jist gem with GitHub"
opt.on :m, :method, "Gist a method's source.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
self.content << meth.source << "\n"
self.filename = meth.source_file
end
opt.on :d, :doc, "Gist a method's documentation.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
text.no_color do
self.content << process_comment_markup(meth.doc) << "\n"
end
self.filename = meth.source_file + ".doc"
end
opt.on :k, :command, "Gist a command's source.", :argument => true do |command_name|
command = find_command(command_name)
block = Pry::Method.new(command.block)
self.content << block.source << "\n"
self.filename = block.source_file
end
opt.on :c, :class, "Gist a class or module's source.", :argument => true do |class_name|
mod = Pry::WrappedModule.from_str(class_name, target)
self.content << mod.source << "\n"
self.filename = mod.source_file
end
opt.on :var, "Gist a variable's content.", :argument => true do |variable_name|
begin
obj = target.eval(variable_name)
rescue Pry::RescuableException
raise CommandError, "Gist failed: Invalid variable name: #{variable_name}"
end
def options(opt)
opt.on :login, "Authenticate the jist gem with GitHub"
opt.on :m, :method, "Gist a method's source.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
self.content << meth.source << "\n"
self.filename = meth.source_file
end
opt.on :d, :doc, "Gist a method's documentation.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
text.no_color do
self.content << process_comment_markup(meth.doc) << "\n"
end
self.filename = meth.source_file + ".doc"
end
opt.on :k, :command, "Gist a command's source.", :argument => true do |command_name|
command = find_command(command_name)
block = Pry::Method.new(command.block)
self.content << block.source << "\n"
self.filename = block.source_file
end
opt.on :c, :class, "Gist a class or module's source.", :argument => true do |class_name|
mod = Pry::WrappedModule.from_str(class_name, target)
self.content << mod.source << "\n"
self.filename = mod.source_file
end
opt.on :var, "Gist a variable's content.", :argument => true do |variable_name|
begin
obj = target.eval(variable_name)
rescue Pry::RescuableException
raise CommandError, "Gist failed: Invalid variable name: #{variable_name}"
end
self.content << Pry.config.gist.inspecter.call(obj) << "\n"
end
opt.on :hist, "Gist a range of Readline history lines.", :optional_argument => true, :as => Range, :default => -20..-1 do |range|
h = Pry.history.to_a
self.content << h[one_index_range(convert_to_range(range))].join("\n") << "\n"
end
opt.on :f, :file, "Gist a file.", :argument => true do |file|
self.content << File.read(File.expand_path(file)) << "\n"
self.filename = file
end
opt.on :o, :out, "Gist entries from Pry's output result history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -1 do |range|
range = convert_to_range(range)
range.each do |v|
self.content << Pry.config.gist.inspecter.call(_pry_.output_array[v])
end
self.content << "\n"
end
opt.on :clip, "Copy the selected content to clipboard instead, do NOT gist it.", :default => false
opt.on :p, :public, "Create a public gist (default: false)", :default => false
opt.on :l, :lines, "Only gist a subset of lines from the gistable content.", :optional_argument => true, :as => Range, :default => 1..-1
opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -1 do |range|
range = convert_to_range(range)
input_expressions = _pry_.input_array[range] || []
Array(input_expressions).each_with_index do |code, index|
corrected_index = index + range.first
if code && code != ""
self.content << code
if code !~ /;\Z/
self.content << "#{comment_expression_result_for_gist(Pry.config.gist.inspecter.call(_pry_.output_array[corrected_index]))}"
end
end
end
end
end
def process
return Jist.login! if opts.present?(:login)
if self.content =~ /\A\s*\z/
raise CommandError, "Found no code to gist."
end
if opts.present?(:clip)
perform_clipboard
else
perform_gist
end
end
# copy content to clipboard instead (only used with --clip flag)
def perform_clipboard
copy(self.content)
output.puts "Copied content to clipboard!"
end
def perform_gist
if opts.present?(:lines)
self.content = restrict_to_lines(content, opts[:l])
end
response = Jist.gist(content, :filename => File.basename(filename),
:public => !!opts[:p])
if response
copy(response['html_url'])
output.puts "Gist created at #{response['html_url']} and added to clipboard."
end
end
def convert_to_range(n)
if !n.is_a?(Range)
(n..n)
else
n
end
end
def comment_expression_result_for_gist(result)
content = ""
result.lines.each_with_index do |line, index|
if index == 0
content << "# => #{line}"
else
content << "# #{line}"
end
end
content
end
# Copy a string to the clipboard.
#
# @param [String] content
#
# @copyright Copyright (c) 2008 Chris Wanstrath (MIT)
# @see https://github.com/defunkt/gist/blob/master/lib/gist.rb#L178
def copy(content)
cmd = case true
when system("type pbcopy > /dev/null 2>&1")
:pbcopy
when system("type xclip > /dev/null 2>&1")
:xclip
when system("type putclip > /dev/null 2>&1")
:putclip
end
if cmd
IO.popen(cmd.to_s, 'r+') { |clip| clip.print content }
end
content
end
self.content << Pry.config.gist.inspecter.call(obj) << "\n"
end
opt.on :hist, "Gist a range of Readline history lines.", :optional_argument => true, :as => Range, :default => -20..-1 do |range|
h = Pry.history.to_a
self.content << h[one_index_range(convert_to_range(range))].join("\n") << "\n"
end
alias_command "clipit", "gist --clip"
alias_command "jist", "gist"
opt.on :f, :file, "Gist a file.", :argument => true do |file|
self.content << File.read(File.expand_path(file)) << "\n"
self.filename = file
end
opt.on :o, :out, "Gist entries from Pry's output result history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -1 do |range|
range = convert_to_range(range)
range.each do |v|
self.content << Pry.config.gist.inspecter.call(_pry_.output_array[v])
end
self.content << "\n"
end
opt.on :clip, "Copy the selected content to clipboard instead, do NOT gist it.", :default => false
opt.on :p, :public, "Create a public gist (default: false)", :default => false
opt.on :l, :lines, "Only gist a subset of lines from the gistable content.", :optional_argument => true, :as => Range, :default => 1..-1
opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -1 do |range|
range = convert_to_range(range)
input_expressions = _pry_.input_array[range] || []
Array(input_expressions).each_with_index do |code, index|
corrected_index = index + range.first
if code && code != ""
self.content << code
if code !~ /;\Z/
self.content << "#{comment_expression_result_for_gist(Pry.config.gist.inspecter.call(_pry_.output_array[corrected_index]))}"
end
end
end
end
end
def process
return Jist.login! if opts.present?(:login)
if self.content =~ /\A\s*\z/
raise CommandError, "Found no code to gist."
end
if opts.present?(:clip)
perform_clipboard
else
perform_gist
end
end
# copy content to clipboard instead (only used with --clip flag)
def perform_clipboard
copy(self.content)
output.puts "Copied content to clipboard!"
end
def perform_gist
if opts.present?(:lines)
self.content = restrict_to_lines(content, opts[:l])
end
response = Jist.gist(content, :filename => File.basename(filename),
:public => !!opts[:p])
if response
copy(response['html_url'])
output.puts "Gist created at #{response['html_url']} and added to clipboard."
end
end
def convert_to_range(n)
if !n.is_a?(Range)
(n..n)
else
n
end
end
def comment_expression_result_for_gist(result)
content = ""
result.lines.each_with_index do |line, index|
if index == 0
content << "# => #{line}"
else
content << "# #{line}"
end
end
content
end
# Copy a string to the clipboard.
#
# @param [String] content
#
# @copyright Copyright (c) 2008 Chris Wanstrath (MIT)
# @see https://github.com/defunkt/gist/blob/master/lib/gist.rb#L178
def copy(content)
cmd = case true
when system("type pbcopy > /dev/null 2>&1")
:pbcopy
when system("type xclip > /dev/null 2>&1")
:xclip
when system("type putclip > /dev/null 2>&1")
:putclip
end
if cmd
IO.popen(cmd.to_s, 'r+') { |clip| clip.print content }
end
content
end
end
Pry::Commands.alias_command "clipit", "gist --clip"
Pry::Commands.alias_command "jist", "gist"
end

View file

@ -1,127 +1,123 @@
class Pry
module DefaultCommands
Help = Pry::CommandSet.new do
create_command "help" do |cmd|
description "Show a list of commands. Type `help <foo>` for information about <foo>."
Pry::Commands.create_command "help" do |cmd|
description "Show a list of commands. Type `help <foo>` for information about <foo>."
banner <<-BANNER
Usage: help [ COMMAND ]
banner <<-BANNER
Usage: help [ COMMAND ]
With no arguments, help lists all the available commands in the current
command-set along with their description.
With no arguments, help lists all the available commands in the current
command-set along with their description.
When given a command name as an argument, shows the help for that command.
BANNER
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
# 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]]
def display_index(groups)
help_text = []
groups.keys.sort_by(&method(:group_sort_key)).each do |key|
commands = groups[key].sort_by{ |command| command.options[:listing].to_s }
unless commands.empty?
help_text << "#{text.bold(key)}\n" + commands.map do |command|
" #{command.options[:listing].to_s.ljust(18)} #{command.description}"
end.join("\n")
end
end
stagger_output(help_text.join("\n\n"))
end
# Display help for an individual command or group.
#
# @param String The string to search for.
def display_search(search)
if command = command_set.find_command_for_help(search)
display_command(command)
else
groups = search_hash(search, command_groups)
if groups.size > 0
display_index(groups)
return
end
# Get a hash of available commands grouped by the "group" name.
def command_groups
visible_commands.values.group_by(&:group)
end
filtered = search_hash(search, visible_commands)
raise CommandError, "No help found for '#{args.first}'" if filtered.empty?
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]]
def display_index(groups)
help_text = []
groups.keys.sort_by(&method(:group_sort_key)).each do |key|
commands = groups[key].sort_by{ |command| command.options[:listing].to_s }
unless commands.empty?
help_text << "#{text.bold(key)}\n" + commands.map do |command|
" #{command.options[:listing].to_s.ljust(18)} #{command.description}"
end.join("\n")
end
end
stagger_output(help_text.join("\n\n"))
end
# Display help for an individual command or group.
#
# @param String The string to search for.
def display_search(search)
if command = command_set.find_command_for_help(search)
display_command(command)
else
groups = search_hash(search, command_groups)
if groups.size > 0
display_index(groups)
return
end
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
end
# Display help for an individual command.
#
# @param [Pry::Command]
def display_command(command)
stagger_output 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] the search term
# @param [Hash] the hash to search
def search_hash(search, hash)
matching = {}
hash.each_pair do |key, value|
next unless key.is_a?(String)
if normalize(key) == normalize(search)
return {key => value}
elsif normalize(key).start_with?(normalize(search))
matching[key] = value
end
end
matching
end
# Clean search terms to make it easier to search group names
#
# @param String
# @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.gsub(' ', '_')) || 99, group_name]
if filtered.size == 1
display_command(filtered.values.first)
else
display_index({"'#{search}' commands" => filtered.values})
end
end
end
# Display help for an individual command.
#
# @param [Pry::Command]
def display_command(command)
stagger_output 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] the search term
# @param [Hash] the hash to search
def search_hash(search, hash)
matching = {}
hash.each_pair do |key, value|
next unless key.is_a?(String)
if normalize(key) == normalize(search)
return {key => value}
elsif normalize(key).start_with?(normalize(search))
matching[key] = value
end
end
matching
end
# Clean search terms to make it easier to search group names
#
# @param String
# @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.gsub(' ', '_')) || 99, group_name]
end
end
end

View file

@ -1,120 +1,112 @@
class Pry
module DefaultCommands
Hist = Pry::CommandSet.new do
Pry::Commands.create_command "hist", "Show and replay Readline history. Aliases: history" do
group "Editing"
create_command "hist", "Show and replay Readline history. Aliases: history" do
group "Editing"
banner <<-USAGE
Usage: hist
hist --head N
hist --tail N
hist --show START..END
hist --grep PATTERN
hist --clear
hist --replay START..END
hist --save [START..END] FILE
USAGE
banner <<-USAGE
Usage: hist
hist --head N
hist --tail N
hist --show START..END
hist --grep PATTERN
hist --clear
hist --replay START..END
hist --save [START..END] FILE
USAGE
def options(opt)
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
def options(opt)
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."
opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
end
opt.on :e, :'exclude-pry', "Exclude Pry commands from the history."
opt.on :n, :'no-numbers', "Omit line numbers."
opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
end
def process
@history = Pry::Code(Pry.history.to_a)
def process
@history = Pry::Code(Pry.history.to_a)
if opts.present?(:show)
@history = @history.between(opts[:show])
end
if opts.present?(:grep)
@history = @history.grep(opts[:grep])
end
@history = case
when opts.present?(:head)
@history.take_lines(1, opts[:head] || 10)
when opts.present?(:tail)
@history.take_lines(-(opts[:tail] || 10), opts[:tail] || 10)
when opts.present?(:show)
@history.between(opts[:show])
else
@history
end
if opts.present?(:'exclude-pry')
@history = @history.select { |l, ln| !command_set.valid_command?(l) }
end
if opts.present?(:save)
process_save
elsif opts.present?(:clear)
process_clear
elsif opts.present?(:replay)
process_replay
else
process_display
end
end
def process_display
unless opts.present?(:'no-numbers')
@history = @history.with_line_numbers
end
render_output(@history, opts)
end
def process_save
case opts[:save]
when Range
@history = @history.between(opts[:save])
unless args.first
raise CommandError, "Must provide a file name."
end
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])
_pry_.input_stack.push _pry_.input
_pry_.input = StringIO.new(@history.raw)
# eval_string << "#{@history.raw}\n"
# run "show-input" unless _pry_.complete_expression?(eval_string)
end
if opts.present?(:show)
@history = @history.between(opts[:show])
end
alias_command "history", "hist"
if opts.present?(:grep)
@history = @history.grep(opts[:grep])
end
@history = case
when opts.present?(:head)
@history.take_lines(1, opts[:head] || 10)
when opts.present?(:tail)
@history.take_lines(-(opts[:tail] || 10), opts[:tail] || 10)
when opts.present?(:show)
@history.between(opts[:show])
else
@history
end
if opts.present?(:'exclude-pry')
@history = @history.select { |l, ln| !command_set.valid_command?(l) }
end
if opts.present?(:save)
process_save
elsif opts.present?(:clear)
process_clear
elsif opts.present?(:replay)
process_replay
else
process_display
end
end
def process_display
unless opts.present?(:'no-numbers')
@history = @history.with_line_numbers
end
render_output(@history, opts)
end
def process_save
case opts[:save]
when Range
@history = @history.between(opts[:save])
unless args.first
raise CommandError, "Must provide a file name."
end
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])
_pry_.input_stack.push _pry_.input
_pry_.input = StringIO.new(@history.raw)
# eval_string << "#{@history.raw}\n"
# run "show-input" unless _pry_.complete_expression?(eval_string)
end
end
Pry::Commands.alias_command "history", "hist"
end

View file

@ -0,0 +1,12 @@
class Pry
Pry::Commands.create_command "import-set", "Import a command set" do
group "Commands"
def process(command_set_name)
raise CommandError, "Provide a command set name" if command_set.nil?
set = target.eval(arg_string)
_pry_.commands.import set
end
end
end

View file

@ -1,306 +0,0 @@
require 'tempfile'
require 'pry/default_commands/gist'
class Pry
module DefaultCommands
InputAndOutput = Pry::CommandSet.new do
import Gist
command(/\.(.*)/, "All text following a '.' is forwarded to the shell.", :listing => ".<shell command>", :use_prefix => false, :takes_block => true) do |cmd|
if cmd =~ /^cd\s+(.+)/i
dest = $1
begin
Dir.chdir File.expand_path(dest)
rescue Errno::ENOENT
raise CommandError, "No such directory: #{dest}"
end
else
pass_block(cmd)
if command_block
command_block.call `#{cmd}`
else
Pry.config.system.call(output, cmd, _pry_)
end
end
end
command "shell-mode", "Toggle shell mode. Bring in pwd prompt and file completion." do
case _pry_.prompt
when Pry::SHELL_PROMPT
_pry_.pop_prompt
_pry_.custom_completions = Pry::DEFAULT_CUSTOM_COMPLETIONS
else
_pry_.push_prompt Pry::SHELL_PROMPT
_pry_.custom_completions = Pry::FILE_COMPLETIONS
Readline.completion_proc = Pry::InputCompleter.build_completion_proc target,
_pry_.instance_eval(&Pry::FILE_COMPLETIONS)
end
end
alias_command "file-mode", "shell-mode"
create_command "save-file", "Export to a file using content from the REPL." do
banner <<-USAGE
Usage: save-file [OPTIONS] [FILE]
Save REPL content to a file.
e.g: save-file -m my_method -m my_method2 ./hello.rb
e.g: save-file -i 1..10 ./hello.rb --append
e.g: save-file -k show-method ./my_command.rb
e.g: save-file -f sample_file --lines 2..10 ./output_file.rb
USAGE
attr_accessor :content
attr_accessor :file_name
def setup
self.content = ""
end
def convert_to_range(n)
if !n.is_a?(Range)
(n..n)
else
n
end
end
def options(opt)
opt.on :m, :method, "Save a method's source.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
self.content << meth.source
end
opt.on :c, :class, "Save a class's source.", :argument => true do |class_name|
mod = Pry::WrappedModule.from_str(class_name, target)
self.content << mod.source
end
opt.on :k, :command, "Save a command's source.", :argument => true do |command_name|
command = find_command(command_name)
block = Pry::Method.new(command.block)
self.content << block.source
end
opt.on :f, :file, "Save a file.", :argument => true do |file|
self.content << File.read(File.expand_path(file))
end
opt.on :l, :lines, "Only save a subset of lines.", :optional_argument => true, :as => Range, :default => 1..-1
opt.on :o, :out, "Save entries from Pry's output result history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -5..-1 do |range|
range = convert_to_range(range)
range.each do |v|
self.content << Pry.config.gist.inspecter.call(_pry_.output_array[v])
end
self.content << "\n"
end
opt.on :i, :in, "Save entries from Pry's input expression history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -5..-1 do |range|
input_expressions = _pry_.input_array[range] || []
Array(input_expressions).each { |v| self.content << v }
end
opt.on :a, :append, "Append to the given file instead of overwriting it."
end
def process
if args.empty?
raise CommandError, "Must specify a file name."
end
self.file_name = File.expand_path(args.first)
save_file
end
def save_file
if self.content.empty?
raise CommandError, "Found no code to save."
end
File.open(file_name, mode) do |f|
if opts.present?(:lines)
f.puts restrict_to_lines(content, opts[:l])
else
f.puts content
end
end
end
def mode
if opts.present?(:append)
"a"
else
"w"
end
end
end
create_command "cat", "Show code from a file, Pry's input buffer, or the last exception." do
banner <<-USAGE
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.
USAGE
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
opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
end
def process
handler = case
when opts.present?(:ex)
method :process_ex
when opts.present?(:in)
method :process_in
else
method :process_file
end
output = handler.call do |code|
code.code_type = opts[:type] || :ruby
code.between(opts[:start] || 1, opts[:end] || -1).
with_line_numbers(opts.present?(:'line-numbers') || opts.present?(:ex))
end
render_output(output, opts)
end
def process_ex
window_size = Pry.config.default_window_size || 5
ex = _pry_.last_exception
raise CommandError, "No exception found." unless ex
if opts[:ex].nil?
bt_index = ex.bt_index
ex.inc_bt_index
else
bt_index = opts[:ex]
ex.bt_index = bt_index
ex.inc_bt_index
end
ex_file, ex_line = ex.bt_source_location_for(bt_index)
raise CommandError, "The given backtrace level is out of bounds." unless ex_file
if RbxPath.is_core_path?(ex_file)
ex_file = RbxPath.convert_path_to_full(ex_file)
end
set_file_and_dir_locals(ex_file)
start_line = ex_line - window_size
start_line = 1 if start_line < 1
end_line = ex_line + window_size
header = unindent <<-HEADER
#{text.bold 'Exception:'} #{ex.class}: #{ex.message}
--
#{text.bold('From:')} #{ex_file} @ line #{ex_line} @ #{text.bold("level: #{bt_index}")} of backtrace (of #{ex.backtrace.size - 1}).
HEADER
code = yield(Pry::Code.from_file(ex_file).
between(start_line, end_line).
with_marker(ex_line))
"#{header}#{code}"
end
def process_in
normalized_range = absolute_index_range(opts[:i], _pry_.input_array.length)
input_items = _pry_.input_array[normalized_range] || []
zipped_items = normalized_range.zip(input_items).reject { |_, s| s.nil? || s == "" }
unless zipped_items.length > 0
raise CommandError, "No expressions found."
end
if zipped_items.length > 1
contents = ""
zipped_items.each do |i, s|
contents << "#{text.bold(i.to_s)}:\n"
contents << yield(Pry::Code(s).with_indentation(2)).to_s
end
else
contents = yield(Pry::Code(zipped_items.first.last))
end
contents
end
def process_file
file_name = args.shift
unless file_name
raise CommandError, "Must provide a filename, --in, or --ex."
end
file_name, line_num = file_name.split(':')
file_name = File.expand_path(file_name)
set_file_and_dir_locals(file_name)
code = yield(Pry::Code.from_file(file_name))
code.code_type = opts[:type] || detect_code_type_from_file(file_name)
if line_num
code = code.around(line_num.to_i,
Pry.config.default_window_size || 7)
end
code
end
def detect_code_type_from_file(file_name)
name, ext = File.basename(file_name).split('.', 2)
if ext
case ext
when "py"
:python
when "rb", "gemspec", "rakefile", "ru"
:ruby
when "js"
return :javascript
when "yml", "prytheme"
:yaml
when "groovy"
:groovy
when "c"
:c
when "cpp"
:cpp
when "java"
:java
else
:text
end
else
case name
when "Rakefile", "Gemfile"
:ruby
else
:text
end
end
end
end
end
end
end

View file

@ -0,0 +1,47 @@
class Pry
Pry::Commands.create_command "install-command", "Install a disabled command." do |name|
group 'Commands'
banner <<-BANNER
Usage: install-command COMMAND
Installs the gems necessary to run the given COMMAND. You will generally not
need to run this unless told to by an error message.
BANNER
def process(name)
require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller
command = find_command(name)
if command_dependencies_met?(command.options)
output.puts "Dependencies for #{command.name} are met. Nothing to do."
return
end
output.puts "Attempting to install `#{name}` command..."
gems_to_install = Array(command.options[:requires_gem])
gems_to_install.each do |g|
next if gem_installed?(g)
output.puts "Installing `#{g}` gem..."
begin
Gem::DependencyInstaller.new.install(g)
rescue Gem::GemNotFoundException
raise CommandError, "Required Gem: `#{g}` not found. Aborting command installation."
end
end
Gem.refresh
gems_to_install.each do |g|
begin
require g
rescue LoadError
raise CommandError, "Required Gem: `#{g}` installed but not found?!. Aborting command installation."
end
end
output.puts "Installation of `#{name}` successful! Type `help #{name}` for information"
end
end
end

View file

@ -1,488 +0,0 @@
require 'tempfile'
class Pry
module DefaultCommands
# For show-doc and show-source
module ModuleIntrospectionHelpers
attr_accessor :module_object
def module_object
if @module_object
@module_object
else
name = args.first
@module_object = WrappedModule.from_str(name, target)
if @module_object
sup = @module_object.ancestors.select do |anc|
anc.class == @module_object.wrapped.class
end[opts[:super]]
@module_object = sup ? Pry::WrappedModule(sup) : nil
end
end
end
# @param [String]
# @param [Binding] target The binding context of the input.
# @return [Symbol] type of input
def input_type(input,target)
if input == ""
:blank
elsif target.eval("defined? #{input} ") =~ /variable|constant/ &&
target.eval(input).respond_to?(:source_location)
:sourcable_object
elsif Pry::Method.from_str(input,target)
:method
elsif Pry::WrappedModule.from_str(input, target)
:module
else
:unknown
end
end
def process(name)
input = args.join(" ")
type = input_type(input, target)
code_or_doc = case type
when :blank
process_blank
when :sourcable_object
process_sourcable_object
when :method
process_method
when :module
process_module
else
command_error("method or module for '#{input}' could not be found or derived", false)
end
render_output(code_or_doc, opts)
end
def process_blank
if mod = extract_module_from_internal_binding
@module_object = mod
process_module
elsif meth = extract_method_from_binding
@method_object = meth
process_method
else
command_error("method or module for '' could not be derived", false)
end
end
def extract_module_from_internal_binding
if args.empty? && internal_binding?(target)
mod = target_self.is_a?(Module) ? target_self : target_self.class
Pry::WrappedModule(mod)
end
end
def extract_method_from_binding
Pry::Method.from_binding(target)
end
def module_start_line(mod, candidate_rank=0)
if opts.present?(:'base-one')
1
else
mod.candidate(candidate_rank).line
end
end
def use_line_numbers?
opts.present?(:b) || opts.present?(:l)
end
def attempt
rank = 0
begin
yield(rank)
rescue Pry::CommandError
raise if rank > (module_object.number_of_candidates - 1)
rank += 1
retry
end
end
end
Introspection = Pry::CommandSet.new do
create_command "show-doc", "Show the documentation for a method or class. Aliases: \?", :shellwords => false do
include ModuleIntrospectionHelpers
include Helpers::DocumentationHelpers
extend Helpers::BaseHelpers
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.
e.g show-doc hello_method # docs for hello_method
e.g show-doc Pry # docs for Pry class
e.g show-doc Pry -a # docs for all definitions of Pry class (all monkey patches)
BANNER
options :requires_gem => "ruby18_source_location" if mri_18?
def setup
require 'ruby18_source_location' if mri_18?
end
def options(opt)
method_options(opt)
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 :f, :flood, "Do not use a pager to view text longer than one screen."
opt.on :a, :all, "Show docs for all definitions and monkeypatches of the module/class"
end
def process_sourcable_object
name = args.first
object = target.eval(name)
file_name, line = object.source_location
doc = Pry::Code.from_file(file_name).comment_describing(line)
doc = strip_leading_hash_and_whitespace_from_ruby_comments(doc)
result = ""
result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n"
result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{doc.lines.count}\n\n"
result << doc
result << "\n"
end
def process_module
raise Pry::CommandError, "No documentation found." if module_object.nil?
if opts.present?(:all)
all_modules
else
normal_module
end
end
def normal_module
doc = ""
if module_object.yard_docs?
file_name, line = module_object.yard_file, module_object.yard_line
doc << module_object.yard_doc
start_line = 1
else
attempt do |rank|
file_name, line = module_object.candidate(rank).source_location
set_file_and_dir_locals(file_name)
doc << module_object.candidate(rank).doc
start_line = module_start_line(module_object, rank)
end
end
doc = Code.new(doc, start_line, :text).
with_line_numbers(use_line_numbers?).to_s
doc.insert(0, "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line ? line : "N/A"}:\n\n")
end
def all_modules
doc = ""
doc << "Found #{module_object.number_of_candidates} candidates for `#{module_object.name}` definition:\n"
module_object.number_of_candidates.times do |v|
candidate = module_object.candidate(v)
begin
doc << "\nCandidate #{v+1}/#{module_object.number_of_candidates}: #{candidate.file} @ #{candidate.line}:\n\n"
doc << candidate.doc
rescue Pry::RescuableException
doc << "No documentation found.\n"
next
end
end
doc
end
def process_method
raise Pry::CommandError, "No documentation found." if method_object.doc.nil? || method_object.doc.empty?
doc = process_comment_markup(method_object.doc)
output.puts make_header(method_object, doc)
output.puts "#{text.bold("Owner:")} #{method_object.owner || "N/A"}"
output.puts "#{text.bold("Visibility:")} #{method_object.visibility}"
output.puts "#{text.bold("Signature:")} #{method_object.signature}"
output.puts
if use_line_numbers?
doc = Code.new(doc, start_line, :text).
with_line_numbers(true).to_s
end
doc
end
def module_start_line(mod, candidate=0)
if opts.present?(:'base-one')
1
else
if mod.candidate(candidate).line
mod.candidate(candidate).line - mod.candidate(candidate).doc.lines.count
else
1
end
end
end
def start_line
if opts.present?(:'base-one')
1
else
(method_object.source_line - method_object.doc.lines.count) || 1
end
end
end
alias_command "?", "show-doc"
create_command "stat", "View method information and set _file_ and _dir_ locals.", :shellwords => false do
banner <<-BANNER
Usage: stat [OPTIONS] [METH]
Show method information for method METH and set _file_ and _dir_ locals.
e.g: stat hello_method
BANNER
def options(opt)
method_options(opt)
end
def process
meth = method_object
output.puts unindent <<-EOS
Method Information:
--
Name: #{meth.name}
Owner: #{meth.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."}
EOS
end
end
create_command "show-source" do
include ModuleIntrospectionHelpers
extend Helpers::BaseHelpers
description "Show the source for a method or class. Aliases: $, show-method"
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.
e.g: `show-source hello_method`
e.g: `show-source -m hello_method`
e.g: `show-source Pry#rep` # source for Pry#rep method
e.g: `show-source Pry` # source for Pry class
e.g: `show-source Pry -a` # source for all Pry class definitions (all monkey patches)
e.g: `show-source Pry --super # source for superclass of Pry (Object class)
https://github.com/pry/pry/wiki/Source-browsing#wiki-Show_method
BANNER
options :shellwords => false
options :requires_gem => "ruby18_source_location" if mri_18?
def setup
require 'ruby18_source_location' if mri_18?
end
def options(opt)
method_options(opt)
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 :f, :flood, "Do not use a pager to view text longer than one screen."
opt.on :a, :all, "Show source for all definitions and monkeypatches of the module/class"
end
def process_sourcable_object
name = args.first
object = target.eval(name)
file_name, line = object.source_location
source = Pry::Code.from_file(file_name).expression_at(line)
code = Pry::Code.new(source).with_line_numbers(use_line_numbers?).to_s
result = ""
result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n"
result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n"
result << code
result << "\n"
end
def process_method
raise CommandError, "Could not find method source" unless method_object.source
code = ""
code << make_header(method_object)
code << "#{text.bold("Owner:")} #{method_object.owner || "N/A"}\n"
code << "#{text.bold("Visibility:")} #{method_object.visibility}\n"
code << "\n"
code << Code.from_method(method_object, start_line).
with_line_numbers(use_line_numbers?).to_s
end
def process_module
raise Pry::CommandError, "No documentation found." if module_object.nil?
if opts.present?(:all)
all_modules
else
normal_module
end
end
def normal_module
file_name = line = code = nil
attempt do |rank|
file_name, line = module_object.candidate(rank).source_location
set_file_and_dir_locals(file_name)
code = Code.from_module(module_object, module_start_line(module_object, rank), rank).
with_line_numbers(use_line_numbers?).to_s
end
result = ""
result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n"
result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n"
result << code
end
def all_modules
mod = module_object
result = ""
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.file} @ line #{candidate.line}:\n"
code = Code.from_module(mod, module_start_line(mod, v), v).
with_line_numbers(use_line_numbers?).to_s
result << "Number of lines: #{code.lines.count}\n\n"
result << code
rescue Pry::RescuableException
result << "\nNo code found.\n"
next
end
end
result
end
def use_line_numbers?
opts.present?(:b) || opts.present?(:l)
end
def start_line
if opts.present?(:'base-one')
1
else
method_object.source_line || 1
end
end
end
alias_command "show-method", "show-source"
alias_command "$", "show-source"
command "show-command", "Show the source for CMD." do |*args|
target = target()
opts = Slop.parse!(args) do |opt|
opt.banner unindent <<-USAGE
Usage: show-command [OPTIONS] [CMD]
Show the source for command CMD.
e.g: show-command show-method
USAGE
opt.on :l, "line-numbers", "Show line numbers."
opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
opt.on :h, :help, "This message." do
output.puts opt.help
end
end
next if opts.present?(:help)
command_name = args.shift
if !command_name
raise CommandError, "You must provide a command name."
end
if find_command(command_name)
block = Pry::Method.new(find_command(command_name).block)
next unless block.source
set_file_and_dir_locals(block.source_file)
output.puts make_header(block)
output.puts
code = Code.from_method(block).with_line_numbers(opts.present?(:'line-numbers')).to_s
render_output(code, opts)
else
raise CommandError, "No such command: #{command_name}."
end
end
create_command "ri", "View ri documentation. e.g `ri Array#each`" do
banner <<-BANNER
Usage: ri [spec]
e.g. ri Array#each
Relies on the rdoc gem being installed. See also: show-doc.
BANNER
def process(spec)
# 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 page
Pry::Helpers::BaseHelpers.lesspipe {|less| yield less}
end
def formatter(io)
if @formatter_klass then
@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 insance.
ri = RDoc::RI::PryDriver.new :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
end
end
end

View file

@ -0,0 +1,17 @@
class Pry
Pry::Commands.command "jump-to", "Jump to a binding further up the stack, popping all bindings below." do |break_level|
break_level = break_level.to_i
nesting_level = _pry_.binding_stack.size - 1
case break_level
when nesting_level
output.puts "Already at nesting level #{nesting_level}"
when (0...nesting_level)
_pry_.binding_stack.slice!(break_level + 1, _pry_.binding_stack.size)
else
max_nest_level = nesting_level - 1
output.puts "Invalid nest level. Must be between 0 and #{max_nest_level}. Got #{break_level}."
end
end
end

View file

@ -1,272 +1,266 @@
class Pry
module DefaultCommands
Pry::Commands.create_command "ls","Show the list of vars and methods in the current scope.",
:shellwords => false, :interpolate => false do
Ls = Pry::CommandSet.new do
group "Context"
create_command "ls","Show the list of vars and methods in the current scope.",
:shellwords => false, :interpolate => false do
def options(opt)
opt.banner unindent <<-USAGE
Usage: ls [-m|-M|-p|-pM] [-q|-v] [-c|-i] [Object]
ls [-g] [-l]
group "Context"
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.
def options(opt)
opt.banner unindent <<-USAGE
Usage: ls [-m|-M|-p|-pM] [-q|-v] [-c|-i] [Object]
ls [-g] [-l]
The colours used are configurable using Pry.config.ls.*_color, and the separator is Pry.config.ls.separator.
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.
Pry.config.ls.ceiling is used to hide methods defined higher up in the inheritance chain, this is by default set to [Object, Module, Class] so that methods defined on all Objects are omitted. The -v flag can be used to ignore this setting and show all methods, while the -q can be used to set the ceiling much lower and show only methods defined on the object or its direct class.
USAGE
The colours used are configurable using Pry.config.ls.*_color, and the separator is Pry.config.ls.separator.
opt.on :m, "methods", "Show public methods defined on the Object (default)"
opt.on :M, "instance-methods", "Show methods defined in a Module or Class"
Pry.config.ls.ceiling is used to hide methods defined higher up in the inheritance chain, this is by default set to [Object, Module, Class] so that methods defined on all Objects are omitted. The -v flag can be used to ignore this setting and show all methods, while the -q can be used to set the ceiling much lower and show only methods defined on the object or its direct class.
USAGE
opt.on :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 :m, "methods", "Show public methods defined on the Object (default)"
opt.on :M, "instance-methods", "Show methods defined in a Module or Class"
opt.on :g, "globals", "Show global variables, including those builtin to Ruby (in cyan)"
opt.on :l, "locals", "Show locals, including those provided by Pry (in red)"
opt.on :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 :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 :g, "globals", "Show global variables, including those builtin to Ruby (in cyan)"
opt.on :l, "locals", "Show locals, including those provided by Pry (in red)"
opt.on :i, "ivars", "Show instance variables (in blue) and class variables (in bright blue)"
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 :G, "grep", "Filter output by regular expression", :argument => true
if jruby?
opt.on :J, "all-java", "Show all the aliases for methods from java (default is to show only prettiest)"
end
end
opt.on :i, "ivars", "Show instance variables (in blue) and class variables (in bright blue)"
def process
obj = args.empty? ? target_self : target.eval(args.join(" "))
opt.on :G, "grep", "Filter output by regular expression", :argument => true
if 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.
has_opts = (opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) ||
opts.present?(:globals) || opts.present?(:locals) || opts.present?(:constants) ||
opts.present?(:ivars))
show_methods = opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) || !has_opts
show_self_methods = (!has_opts && Module === obj)
show_constants = opts.present?(:constants) || (!has_opts && Module === obj)
show_ivars = opts.present?(:ivars) || !has_opts
show_locals = opts.present?(:locals) || (!has_opts && args.empty?)
grep_regex, grep = [Regexp.new(opts[:G] || "."), lambda{ |x| x.grep(grep_regex) }]
raise Pry::CommandError, "-l does not make sense with a specified Object" if opts.present?(:locals) && !args.empty?
raise Pry::CommandError, "-g does not make sense with a specified Object" if opts.present?(:globals) && !args.empty?
raise Pry::CommandError, "-q does not make sense with -v" if opts.present?(:quiet) && opts.present?(:verbose)
raise Pry::CommandError, "-M only makes sense with a Module or a Class" if opts.present?(:'instance-methods') && !(Module === obj)
raise Pry::CommandError, "-c only makes sense with a Module or a Class" if opts.present?(:constants) && !args.empty? && !(Module === obj)
if opts.present?(:globals)
output_section("global variables", grep[format_globals(target.eval("global_variables"))])
end
if show_constants
mod = Module === obj ? obj : Object
constants = mod.constants
constants -= (mod.ancestors - [mod]).map(&:constants).flatten unless opts.present?(:verbose)
output_section("constants", grep[format_constants(mod, constants)])
end
if show_methods
# methods is a hash {Module/Class => [Pry::Methods]}
methods = all_methods(obj).group_by(&:owner)
# reverse the resolution order so that the most useful information appears right by the prompt
resolution_order(obj).take_while(&below_ceiling(obj)).reverse.each do |klass|
methods_here = format_methods((methods[klass] || []).select{ |m| m.name =~ grep_regex })
output_section "#{Pry::WrappedModule.new(klass).method_prefix}methods", methods_here
end
end
if show_self_methods
methods = all_methods(obj, true).select{ |m| m.owner == obj && m.name =~ grep_regex }
output_section "#{Pry::WrappedModule.new(obj).method_prefix}methods", format_methods(methods)
end
if show_ivars
klass = (Module === obj ? obj : obj.class)
ivars = Pry::Method.safe_send(obj, :instance_variables)
kvars = Pry::Method.safe_send(klass, :class_variables)
output_section("instance variables", format_variables(:instance_var, ivars))
output_section("class variables", format_variables(:class_var, kvars))
end
if show_locals
output_section("locals", format_locals(grep[target.eval("local_variables")]))
end
end
private
# http://ruby.runpaint.org/globals, and running "puts global_variables.inspect".
BUILTIN_GLOBALS = %w($" $$ $* $, $-0 $-F $-I $-K $-W $-a $-d $-i $-l $-p $-v $-w $. $/ $\\
$: $; $< $= $> $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)
# $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)
# Get all the methods that we'll want to output
def all_methods(obj, instance_methods=false)
methods = if instance_methods || opts.present?(:'instance-methods')
Pry::Method.all_from_class(obj)
else
Pry::Method.all_from_obj(obj)
end
if jruby? && !opts.present?(:J)
methods = trim_jruby_aliases(methods)
end
methods.select{ |method| opts.present?(:ppp) || method.visibility == :public }
end
# 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.map do |key, values|
values = values.sort_by do |m|
rubbishness(m.name)
end
def process
obj = args.empty? ? target_self : target.eval(args.join(" "))
# exclude -q, -v and --grep because they don't specify what the user wants to see.
has_opts = (opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) ||
opts.present?(:globals) || opts.present?(:locals) || opts.present?(:constants) ||
opts.present?(:ivars))
show_methods = opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) || !has_opts
show_self_methods = (!has_opts && Module === obj)
show_constants = opts.present?(:constants) || (!has_opts && Module === obj)
show_ivars = opts.present?(:ivars) || !has_opts
show_locals = opts.present?(:locals) || (!has_opts && args.empty?)
grep_regex, grep = [Regexp.new(opts[:G] || "."), lambda{ |x| x.grep(grep_regex) }]
raise Pry::CommandError, "-l does not make sense with a specified Object" if opts.present?(:locals) && !args.empty?
raise Pry::CommandError, "-g does not make sense with a specified Object" if opts.present?(:globals) && !args.empty?
raise Pry::CommandError, "-q does not make sense with -v" if opts.present?(:quiet) && opts.present?(:verbose)
raise Pry::CommandError, "-M only makes sense with a Module or a Class" if opts.present?(:'instance-methods') && !(Module === obj)
raise Pry::CommandError, "-c only makes sense with a Module or a Class" if opts.present?(:constants) && !args.empty? && !(Module === obj)
if opts.present?(:globals)
output_section("global variables", grep[format_globals(target.eval("global_variables"))])
end
if show_constants
mod = Module === obj ? obj : Object
constants = mod.constants
constants -= (mod.ancestors - [mod]).map(&:constants).flatten unless opts.present?(:verbose)
output_section("constants", grep[format_constants(mod, constants)])
end
if show_methods
# methods is a hash {Module/Class => [Pry::Methods]}
methods = all_methods(obj).group_by(&:owner)
# reverse the resolution order so that the most useful information appears right by the prompt
resolution_order(obj).take_while(&below_ceiling(obj)).reverse.each do |klass|
methods_here = format_methods((methods[klass] || []).select{ |m| m.name =~ grep_regex })
output_section "#{Pry::WrappedModule.new(klass).method_prefix}methods", methods_here
end
end
if show_self_methods
methods = all_methods(obj, true).select{ |m| m.owner == obj && m.name =~ grep_regex }
output_section "#{Pry::WrappedModule.new(obj).method_prefix}methods", format_methods(methods)
end
if show_ivars
klass = (Module === obj ? obj : obj.class)
ivars = Pry::Method.safe_send(obj, :instance_variables)
kvars = Pry::Method.safe_send(klass, :class_variables)
output_section("instance variables", format_variables(:instance_var, ivars))
output_section("class variables", format_variables(:class_var, kvars))
end
if show_locals
output_section("locals", format_locals(grep[target.eval("local_variables")]))
end
found = []
values.select do |x|
(!found.any?{ |y| x == y }) && found << x
end
end.flatten(1)
end
private
# http://ruby.runpaint.org/globals, and running "puts global_variables.inspect".
BUILTIN_GLOBALS = %w($" $$ $* $, $-0 $-F $-I $-K $-W $-a $-d $-i $-l $-p $-v $-w $. $/ $\\
$: $; $< $= $> $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)
# $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)
# Get all the methods that we'll want to output
def all_methods(obj, instance_methods=false)
methods = if instance_methods || opts.present?(:'instance-methods')
Pry::Method.all_from_class(obj)
else
Pry::Method.all_from_obj(obj)
end
if jruby? && !opts.present?(:J)
methods = trim_jruby_aliases(methods)
end
methods.select{ |method| opts.present?(:ppp) || method.visibility == :public }
# 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{ |x|
case x
when /[A-Z]/
1
when '?', '=', '!'
-2
else
0
end
}.inject(&:+) + (name.size / 100.0)
end
# 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
def resolution_order(obj)
opts.present?(:'instance-methods') ? Pry::Method.instance_resolution_order(obj) : Pry::Method.resolution_order(obj)
end
grouped.map do |key, values|
values = values.sort_by do |m|
rubbishness(m.name)
end
# Get a lambda that can be used with .take_while to prevent over-eager
# traversal of the Object's ancestry graph.
def below_ceiling(obj)
ceiling = if opts.present?(:quiet)
[opts.present?(:'instance-methods') ? obj.ancestors[1] : obj.class.ancestors[1]] + Pry.config.ls.ceiling
elsif opts.present?(:verbose)
[]
else
Pry.config.ls.ceiling.dup
end
found = []
values.select do |x|
(!found.any?{ |y| x == y }) && found << x
end
end.flatten(1)
end
lambda { |klass| !ceiling.include?(klass) }
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{ |x|
case x
when /[A-Z]/
1
when '?', '=', '!'
-2
else
0
end
}.inject(&:+) + (name.size / 100.0)
end
def resolution_order(obj)
opts.present?(:'instance-methods') ? Pry::Method.instance_resolution_order(obj) : Pry::Method.resolution_order(obj)
end
# Get a lambda that can be used with .take_while to prevent over-eager
# traversal of the Object's ancestry graph.
def below_ceiling(obj)
ceiling = if opts.present?(:quiet)
[opts.present?(:'instance-methods') ? obj.ancestors[1] : obj.class.ancestors[1]] + Pry.config.ls.ceiling
elsif opts.present?(:verbose)
[]
else
Pry.config.ls.ceiling.dup
end
lambda { |klass| !ceiling.include?(klass) }
end
# Format and colourise a list of methods.
def format_methods(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
def format_variables(type, vars)
vars.sort_by(&:downcase).map{ |var| color(type, var) }
end
def format_constants(mod, constants)
constants.sort_by(&:downcase).map do |name|
if const = (!mod.autoload?(name) && (mod.const_get(name) || true) rescue nil)
if (const < Exception rescue false)
color(:exception_constant, name)
elsif (Module === mod.const_get(name) rescue false)
color(:class_constant, name)
else
color(:constant, name)
end
else
color(:unloaded_constant, name)
end
end
end
def format_globals(globals)
globals.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
def format_locals(locals)
locals.sort_by(&:downcase).map do |name|
if _pry_.sticky_locals.include?(name.to_sym)
color(:pry_var, name)
else
color(:local_var, name)
end
end
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?
output.puts "#{text.bold(color(:heading, heading))}: #{body.compact.join(Pry.config.ls.separator)}"
end
# Color output based on config.ls.*_color
def color(type, str)
text.send(Pry.config.ls.send(:"#{type}_color"), str)
# Format and colourise a list of methods.
def format_methods(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
def format_variables(type, vars)
vars.sort_by(&:downcase).map{ |var| color(type, var) }
end
def format_constants(mod, constants)
constants.sort_by(&:downcase).map do |name|
if const = (!mod.autoload?(name) && (mod.const_get(name) || true) rescue nil)
if (const < Exception rescue false)
color(:exception_constant, name)
elsif (Module === mod.const_get(name) rescue false)
color(:class_constant, name)
else
color(:constant, name)
end
else
color(:unloaded_constant, name)
end
end
end
def format_globals(globals)
globals.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
def format_locals(locals)
locals.sort_by(&:downcase).map do |name|
if _pry_.sticky_locals.include?(name.to_sym)
color(:pry_var, name)
else
color(:local_var, name)
end
end
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?
output.puts "#{text.bold(color(:heading, heading))}: #{body.compact.join(Pry.config.ls.separator)}"
end
# Color output based on config.ls.*_color
def color(type, str)
text.send(Pry.config.ls.send(:"#{type}_color"), str)
end
end
end

View file

@ -1,38 +0,0 @@
class Pry
module DefaultCommands
Misc = Pry::CommandSet.new do
command "toggle-color", "Toggle syntax highlighting." do
Pry.color = !Pry.color
output.puts "Syntax highlighting #{Pry.color ? "on" : "off"}"
end
command "simple-prompt", "Toggle the simple prompt." do
case _pry_.prompt
when Pry::SIMPLE_PROMPT
_pry_.pop_prompt
else
_pry_.push_prompt Pry::SIMPLE_PROMPT
end
end
command "pry-version", "Show Pry version." do
output.puts "Pry version: #{Pry::VERSION} on Ruby #{RUBY_VERSION}."
end
command "reload-method", "Reload the source file that contains the specified method" do |meth_name|
meth = get_method_or_raise(meth_name, target, {}, :omit_help)
if meth.source_type == :c
raise CommandError, "Can't reload a C method."
elsif meth.dynamically_defined?
raise CommandError, "Can't reload an eval method."
else
file_name = meth.source_file
load file_name
output.puts "Reloaded #{file_name}."
end
end
end
end
end

View file

@ -1,110 +0,0 @@
class Pry
module DefaultCommands
NavigatingPry = Pry::CommandSet.new do
command "switch-to", "Start a new sub-session on a binding in the current stack (numbered by nesting)." do |selection|
selection = selection.to_i
if selection < 0 || selection > _pry_.binding_stack.size - 1
raise CommandError, "Invalid binding index #{selection} - use `nesting` command to view valid indices."
else
Pry.start(_pry_.binding_stack[selection])
end
end
command "nesting", "Show nesting information." do
output.puts "Nesting status:"
output.puts "--"
_pry_.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
command "jump-to", "Jump to a binding further up the stack, popping all bindings below." do |break_level|
break_level = break_level.to_i
nesting_level = _pry_.binding_stack.size - 1
case break_level
when nesting_level
output.puts "Already at nesting level #{nesting_level}"
when (0...nesting_level)
_pry_.binding_stack.slice!(break_level + 1, _pry_.binding_stack.size)
else
max_nest_level = nesting_level - 1
output.puts "Invalid nest level. Must be between 0 and #{max_nest_level}. Got #{break_level}."
end
end
command "exit-all", "End the current Pry session (popping all bindings) and returning to caller. Accepts optional return value. Aliases: !!@" do
# calculate user-given value
exit_value = target.eval(arg_string)
# clear the binding stack
_pry_.binding_stack.clear
# break out of the repl loop
throw(:breakout, exit_value)
end
alias_command "!!@", "exit-all"
create_command "exit" do
description "Pop the previous binding (does NOT exit program). Aliases: quit"
banner <<-BANNER
Usage: exit [OPTIONS] [--help]
Aliases: quit
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.
e.g: `exit "pry this"`
e.g: `exit`
https://github.com/pry/pry/wiki/State-navigation#wiki-Exit_with_value
BANNER
command_options(
:keep_retval => true
)
def process
if _pry_.binding_stack.one?
_pry_.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_.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
alias_command "quit", "exit"
command "exit-program", "End the current program. Aliases: quit-program, !!!" do
Pry.save_history if Pry.config.history.should_save
Kernel.exit target.eval(arg_string).to_i
end
alias_command "quit-program", "exit-program"
alias_command "!!!", "exit-program"
command "!pry", "Start a Pry session on current self; this even works mid multi-line expression." do
target.pry
end
end
end
end

View file

@ -0,0 +1,13 @@
class Pry
Pry::Commands.command "nesting", "Show nesting information." do
output.puts "Nesting status:"
output.puts "--"
_pry_.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

View file

@ -0,0 +1,84 @@
class Pry
Pry::Commands.create_command "play" do
include Pry::Helpers::DocumentationHelpers
description "Play back a string variable or a method or a 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. Default action (no
options) is to play the provided string variable
e.g: `play -i 20 --lines 1..3`
e.g: `play -m Pry#repl --lines 1..-1`
e.g: `play -f Rakefile --lines 5`
https://github.com/pry/pry/wiki/User-Input#wiki-Play
BANNER
attr_accessor :content
def setup
self.content = ""
end
def options(opt)
opt.on :m, :method, "Play a method's source.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
self.content << meth.source
end
opt.on :d, :doc, "Play a method's documentation.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
text.no_color do
self.content << process_comment_markup(meth.doc)
end
end
opt.on :c, :command, "Play a command's source.", :argument => true do |command_name|
command = find_command(command_name)
block = Pry::Method.new(command.block)
self.content << block.source
end
opt.on :f, :file, "Play a file.", :argument => true do |file|
self.content << File.read(File.expand_path(file))
end
opt.on :l, :lines, "Only play a subset of lines.", :optional_argument => true, :as => Range, :default => 1..-1
opt.on :i, :in, "Play entries from Pry's input expression history. Takes an index or range. Note this can only replay pure Ruby code, not Pry commands.", :optional_argument => true,
:as => Range, :default => -5..-1 do |range|
input_expressions = _pry_.input_array[range] || []
Array(input_expressions).each { |v| self.content << v }
end
opt.on :o, "open", 'When used with the -m switch, it plays the entire method except the last line, leaving the method definition "open". `amend-line` can then be used to modify the method.'
end
def process
perform_play
run "show-input" unless Pry::Code.complete_expression?(eval_string)
end
def process_non_opt
args.each do |arg|
begin
self.content << target.eval(arg)
rescue Pry::RescuableException
raise CommandError, "Problem when evaling #{arg}."
end
end
end
def perform_play
process_non_opt
if opts.present?(:lines)
self.content = restrict_to_lines(self.content, opts[:l])
end
if opts.present?(:open)
self.content = restrict_to_lines(self.content, 1..-2)
end
eval_string << self.content
end
end
end

View file

@ -0,0 +1,21 @@
class Pry
Pry::Commands.create_command "pry-backtrace", "Show the backtrace for the Pry session." do
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)
e.g: pry-backtrace
BANNER
def process
output.puts "\n#{text.bold('Backtrace:')}\n--\n"
stagger_output _pry_.backtrace.join("\n")
end
end
end

View file

@ -0,0 +1,5 @@
class Pry
Pry::Commands.command "pry-version", "Show Pry version." do
output.puts "Pry version: #{Pry::VERSION} on Ruby #{RUBY_VERSION}."
end
end

View file

@ -0,0 +1,23 @@
class Pry
# N.B. using a regular expresion here so that "raise-up 'foo'" does the right thing.
Pry::Commands.create_command(/raise-up(!?\b.*)/, :listing => 'raise-up') do
description "Raise an exception out of the current pry instance."
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_).
e.g. `raise-up "get-me-out-of-here"` is equivalent to:
`raise "get-me-out-of-here"
raise-up`
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.
BANNER
def process
return stagger_output help if captures[0] =~ /(-h|--help)\b/
# Handle 'raise-up', 'raise-up "foo"', 'raise-up RuntimeError, 'farble' in a rubyesque manner
target.eval("_pry_.raise_up#{captures[0]}")
end
end
end

View file

@ -0,0 +1,15 @@
class Pry
Pry::Commands.command "reload-method", "Reload the source file that contains the specified method" do |meth_name|
meth = get_method_or_raise(meth_name, target, {}, :omit_help)
if meth.source_type == :c
raise CommandError, "Can't reload a C method."
elsif meth.dynamically_defined?
raise CommandError, "Can't reload an eval method."
else
file_name = meth.source_file
load file_name
output.puts "Reloaded #{file_name}."
end
end
end

View file

@ -0,0 +1,6 @@
class Pry
Pry::Commands.command "reset", "Reset the REPL to a clean state." do
output.puts "Pry reset."
exec "pry"
end
end

View file

@ -0,0 +1,46 @@
class Pry
Pry::Commands.create_command "ri", "View ri documentation. e.g `ri Array#each`" do
banner <<-BANNER
Usage: ri [spec]
e.g. ri Array#each
Relies on the rdoc gem being installed. See also: show-doc.
BANNER
def process(spec)
# 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 page
Pry::Helpers::BaseHelpers.lesspipe {|less| yield less}
end
def formatter(io)
if @formatter_klass then
@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 insance.
ri = RDoc::RI::PryDriver.new :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
end

View file

@ -0,0 +1,95 @@
class Pry
Pry::Commands.create_command "save-file", "Export to a file using content from the REPL." do
banner <<-USAGE
Usage: save-file [OPTIONS] [FILE]
Save REPL content to a file.
e.g: save-file -m my_method -m my_method2 ./hello.rb
e.g: save-file -i 1..10 ./hello.rb --append
e.g: save-file -k show-method ./my_command.rb
e.g: save-file -f sample_file --lines 2..10 ./output_file.rb
USAGE
attr_accessor :content
attr_accessor :file_name
def setup
self.content = ""
end
def convert_to_range(n)
if !n.is_a?(Range)
(n..n)
else
n
end
end
def options(opt)
opt.on :m, :method, "Save a method's source.", :argument => true do |meth_name|
meth = get_method_or_raise(meth_name, target, {})
self.content << meth.source
end
opt.on :c, :class, "Save a class's source.", :argument => true do |class_name|
mod = Pry::WrappedModule.from_str(class_name, target)
self.content << mod.source
end
opt.on :k, :command, "Save a command's source.", :argument => true do |command_name|
command = find_command(command_name)
block = Pry::Method.new(command.block)
self.content << block.source
end
opt.on :f, :file, "Save a file.", :argument => true do |file|
self.content << File.read(File.expand_path(file))
end
opt.on :l, :lines, "Only save a subset of lines.", :optional_argument => true, :as => Range, :default => 1..-1
opt.on :o, :out, "Save entries from Pry's output result history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -5..-1 do |range|
range = convert_to_range(range)
range.each do |v|
self.content << Pry.config.gist.inspecter.call(_pry_.output_array[v])
end
self.content << "\n"
end
opt.on :i, :in, "Save entries from Pry's input expression history. Takes an index or range.", :optional_argument => true,
:as => Range, :default => -5..-1 do |range|
input_expressions = _pry_.input_array[range] || []
Array(input_expressions).each { |v| self.content << v }
end
opt.on :a, :append, "Append to the given file instead of overwriting it."
end
def process
if args.empty?
raise CommandError, "Must specify a file name."
end
self.file_name = File.expand_path(args.first)
save_file
end
def save_file
if self.content.empty?
raise CommandError, "Found no code to save."
end
File.open(file_name, mode) do |f|
if opts.present?(:lines)
f.puts restrict_to_lines(content, opts[:l])
else
f.puts content
end
end
end
def mode
if opts.present?(:append)
"a"
else
"w"
end
end
end
end

View file

@ -0,0 +1,20 @@
class Pry
Pry::Commands.command(/\.(.*)/, "All text following a '.' is forwarded to the shell.", :listing => ".<shell command>", :use_prefix => false, :takes_block => true) do |cmd|
if cmd =~ /^cd\s+(.+)/i
dest = $1
begin
Dir.chdir File.expand_path(dest)
rescue Errno::ENOENT
raise CommandError, "No such directory: #{dest}"
end
else
pass_block(cmd)
if command_block
command_block.call `#{cmd}`
else
Pry.config.system.call(output, cmd, _pry_)
end
end
end
end

View file

@ -0,0 +1,16 @@
class Pry
Pry::Commands.command "shell-mode", "Toggle shell mode. Bring in pwd prompt and file completion." do
case _pry_.prompt
when Pry::SHELL_PROMPT
_pry_.pop_prompt
_pry_.custom_completions = Pry::DEFAULT_CUSTOM_COMPLETIONS
else
_pry_.push_prompt Pry::SHELL_PROMPT
_pry_.custom_completions = Pry::FILE_COMPLETIONS
Readline.completion_proc = Pry::InputCompleter.build_completion_proc target,
_pry_.instance_eval(&Pry::FILE_COMPLETIONS)
end
end
Pry::Commands.alias_command "file-mode", "shell-mode"
end

View file

@ -0,0 +1,42 @@
class Pry
Pry::Commands.command "show-command", "Show the source for CMD." do |*args|
target = target()
opts = Slop.parse!(args) do |opt|
opt.banner unindent <<-USAGE
Usage: show-command [OPTIONS] [CMD]
Show the source for command CMD.
e.g: show-command show-method
USAGE
opt.on :l, "line-numbers", "Show line numbers."
opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
opt.on :h, :help, "This message." do
output.puts opt.help
end
end
next if opts.present?(:help)
command_name = args.shift
if !command_name
raise CommandError, "You must provide a command name."
end
if find_command(command_name)
block = Pry::Method.new(find_command(command_name).block)
next unless block.source
set_file_and_dir_locals(block.source_file)
output.puts make_header(block)
output.puts
code = Pry::Code.from_method(block).with_line_numbers(opts.present?(:'line-numbers')).to_s
render_output(code, opts)
else
raise CommandError, "No such command: #{command_name}."
end
end
end

View file

@ -0,0 +1,133 @@
class Pry
Pry::Commands.create_command "show-doc", "Show the documentation for a method or class. Aliases: \?", :shellwords => false do
include Pry::Helpers::ModuleIntrospectionHelpers
include Pry::Helpers::DocumentationHelpers
extend Pry::Helpers::BaseHelpers
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.
e.g show-doc hello_method # docs for hello_method
e.g show-doc Pry # docs for Pry class
e.g show-doc Pry -a # docs for all definitions of Pry class (all monkey patches)
BANNER
options :requires_gem => "ruby18_source_location" if mri_18?
def setup
require 'ruby18_source_location' if mri_18?
end
def options(opt)
method_options(opt)
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 :f, :flood, "Do not use a pager to view text longer than one screen."
opt.on :a, :all, "Show docs for all definitions and monkeypatches of the module/class"
end
def process_sourcable_object
name = args.first
object = target.eval(name)
file_name, line = object.source_location
doc = Pry::Code.from_file(file_name).comment_describing(line)
doc = strip_leading_hash_and_whitespace_from_ruby_comments(doc)
result = ""
result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n"
result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{doc.lines.count}\n\n"
result << doc
result << "\n"
end
def process_module
raise Pry::CommandError, "No documentation found." if module_object.nil?
if opts.present?(:all)
all_modules
else
normal_module
end
end
def normal_module
doc = ""
if module_object.yard_docs?
file_name, line = module_object.yard_file, module_object.yard_line
doc << module_object.yard_doc
start_line = 1
else
attempt do |rank|
file_name, line = module_object.candidate(rank).source_location
set_file_and_dir_locals(file_name)
doc << module_object.candidate(rank).doc
start_line = module_start_line(module_object, rank)
end
end
doc = Pry::Code.new(doc, start_line, :text).
with_line_numbers(use_line_numbers?).to_s
doc.insert(0, "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line ? line : "N/A"}:\n\n")
end
def all_modules
doc = ""
doc << "Found #{module_object.number_of_candidates} candidates for `#{module_object.name}` definition:\n"
module_object.number_of_candidates.times do |v|
candidate = module_object.candidate(v)
begin
doc << "\nCandidate #{v+1}/#{module_object.number_of_candidates}: #{candidate.file} @ #{candidate.line}:\n\n"
doc << candidate.doc
rescue Pry::RescuableException
doc << "No documentation found.\n"
next
end
end
doc
end
def process_method
raise Pry::CommandError, "No documentation found." if method_object.doc.nil? || method_object.doc.empty?
doc = process_comment_markup(method_object.doc)
output.puts make_header(method_object, doc)
output.puts "#{text.bold("Owner:")} #{method_object.owner || "N/A"}"
output.puts "#{text.bold("Visibility:")} #{method_object.visibility}"
output.puts "#{text.bold("Signature:")} #{method_object.signature}"
output.puts
if use_line_numbers?
doc = Pry::Code.new(doc, start_line, :text).
with_line_numbers(true).to_s
end
doc
end
def module_start_line(mod, candidate=0)
if opts.present?(:'base-one')
1
else
if mod.candidate(candidate).line
mod.candidate(candidate).line - mod.candidate(candidate).doc.lines.count
else
1
end
end
end
def start_line
if opts.present?(:'base-one')
1
else
(method_object.source_line - method_object.doc.lines.count) || 1
end
end
end
Pry::Commands.alias_command "?", "show-doc"
end

View file

@ -0,0 +1,7 @@
class Pry
Pry::Commands.create_command "show-input", "Show the contents of the input buffer for the current multi-line expression." do
def process
output.puts Code.new(eval_string).with_line_numbers
end
end
end

View file

@ -0,0 +1,128 @@
class Pry
Pry::Commands.create_command "show-source" do
include Pry::Helpers::ModuleIntrospectionHelpers
extend Pry::Helpers::BaseHelpers
description "Show the source for a method or class. Aliases: $, show-method"
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.
e.g: `show-source hello_method`
e.g: `show-source -m hello_method`
e.g: `show-source Pry#rep` # source for Pry#rep method
e.g: `show-source Pry` # source for Pry class
e.g: `show-source Pry -a` # source for all Pry class definitions (all monkey patches)
e.g: `show-source Pry --super # source for superclass of Pry (Object class)
https://github.com/pry/pry/wiki/Source-browsing#wiki-Show_method
BANNER
options :shellwords => false
options :requires_gem => "ruby18_source_location" if mri_18?
def setup
require 'ruby18_source_location' if mri_18?
end
def options(opt)
method_options(opt)
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 :f, :flood, "Do not use a pager to view text longer than one screen."
opt.on :a, :all, "Show source for all definitions and monkeypatches of the module/class"
end
def process_sourcable_object
name = args.first
object = target.eval(name)
file_name, line = object.source_location
source = Pry::Code.from_file(file_name).expression_at(line)
code = Pry::Code.new(source).with_line_numbers(use_line_numbers?).to_s
result = ""
result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n"
result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n"
result << code
result << "\n"
end
def process_method
raise CommandError, "Could not find method source" unless method_object.source
code = ""
code << make_header(method_object)
code << "#{text.bold("Owner:")} #{method_object.owner || "N/A"}\n"
code << "#{text.bold("Visibility:")} #{method_object.visibility}\n"
code << "\n"
code << Code.from_method(method_object, start_line).
with_line_numbers(use_line_numbers?).to_s
end
def process_module
raise Pry::CommandError, "No documentation found." if module_object.nil?
if opts.present?(:all)
all_modules
else
normal_module
end
end
def normal_module
file_name = line = code = nil
attempt do |rank|
file_name, line = module_object.candidate(rank).source_location
set_file_and_dir_locals(file_name)
code = Code.from_module(module_object, module_start_line(module_object, rank), rank).
with_line_numbers(use_line_numbers?).to_s
end
result = ""
result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n"
result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n"
result << code
end
def all_modules
mod = module_object
result = ""
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.file} @ line #{candidate.line}:\n"
code = Code.from_module(mod, module_start_line(mod, v), v).
with_line_numbers(use_line_numbers?).to_s
result << "Number of lines: #{code.lines.count}\n\n"
result << code
rescue Pry::RescuableException
result << "\nNo code found.\n"
next
end
end
result
end
def use_line_numbers?
opts.present?(:b) || opts.present?(:l)
end
def start_line
if opts.present?(:'base-one')
1
else
method_object.source_line || 1
end
end
end
Pry::Commands.alias_command "show-method", "show-source"
Pry::Commands.alias_command "$", "show-source"
end

View file

@ -0,0 +1,10 @@
class Pry
Pry::Commands.command "simple-prompt", "Toggle the simple prompt." do
case _pry_.prompt
when Pry::SIMPLE_PROMPT
_pry_.pop_prompt
else
_pry_.push_prompt Pry::SIMPLE_PROMPT
end
end
end

View file

@ -0,0 +1,28 @@
class Pry
Pry::Commands.create_command "stat", "View method information and set _file_ and _dir_ locals.", :shellwords => false do
banner <<-BANNER
Usage: stat [OPTIONS] [METH]
Show method information for method METH and set _file_ and _dir_ locals.
e.g: stat hello_method
BANNER
def options(opt)
method_options(opt)
end
def process
meth = method_object
output.puts unindent <<-EOS
Method Information:
--
Name: #{meth.name}
Owner: #{meth.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."}
EOS
end
end
end

View file

@ -0,0 +1,11 @@
class Pry
Pry::Commands.command "switch-to", "Start a new sub-session on a binding in the current stack (numbered by nesting)." do |selection|
selection = selection.to_i
if selection < 0 || selection > _pry_.binding_stack.size - 1
raise CommandError, "Invalid binding index #{selection} - use `nesting` command to view valid indices."
else
Pry.start(_pry_.binding_stack[selection])
end
end
end

View file

@ -0,0 +1,6 @@
class Pry
Pry::Commands.command "toggle-color", "Toggle syntax highlighting." do
Pry.color = !Pry.color
output.puts "Syntax highlighting #{Pry.color ? "on" : "off"}"
end
end

View file

@ -1,91 +1,86 @@
class Pry
module DefaultCommands
Whereami = Pry::CommandSet.new do
create_command "whereami" do
description "Show code surrounding the current context."
group 'Context'
banner <<-BANNER
Usage: whereami [-q] [N]
Pry::Commands.create_command "whereami" do
description "Show code surrounding the current context."
group 'Context'
banner <<-BANNER
Usage: whereami [-q] [N]
Describe the current location. If you use `binding.pry` inside a
method then whereami will print out the source for that method.
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 N lines before and after the current line
will be shown instead of the method itself.
If a number is passed, then N 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 `-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`.
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
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
@method = Pry::Method.from_binding(target)
@file = target.eval('__FILE__')
@line = target.eval('__LINE__')
def setup
@method = Pry::Method.from_binding(target)
@file = target.eval('__FILE__')
@line = target.eval('__LINE__')
end
def options(opt)
opt.on :q, :quiet, "Don't display anything in case of an error"
end
def code
@code ||= if show_method?
Pry::Code.from_method(@method)
else
Pry::Code.from_file(@file).around(@line, window_size)
end
end
def location
"#{@file} @ line #{show_method? ? @method.source_line : @line} #{@method && @method.name_with_owner}"
end
def process
if opts.quiet? && (internal_binding?(target) || !code?)
return
elsif internal_binding?(target)
if target_self == TOPLEVEL_BINDING.eval("self")
output.puts "At the top level."
else
output.puts "Inside #{Pry.view_clip(target_self)}."
end
return
end
def options(opt)
opt.on :q, :quiet, "Don't display anything in case of an error"
end
set_file_and_dir_locals(@file)
def code
@code ||= if show_method?
Pry::Code.from_method(@method)
else
Pry::Code.from_file(@file).around(@line, window_size)
end
end
output.puts "\n#{text.bold('From:')} #{location}:\n\n"
output.puts code.with_line_numbers.with_marker(@line)
output.puts
end
def location
"#{@file} @ line #{show_method? ? @method.source_line : @line} #{@method && @method.name_with_owner}"
end
private
def process
if opts.quiet? && (internal_binding?(target) || !code?)
return
elsif internal_binding?(target)
if target_self == TOPLEVEL_BINDING.eval("self")
output.puts "At the top level."
else
output.puts "Inside #{Pry.view_clip(target_self)}."
end
return
end
def show_method?
args.empty? && @method && @method.source? && @method.source_range.count < 20 &&
# These checks are needed in case of an eval with a binding and file/line
# numbers set to outside the function. As in rails' use of ERB.
@method.source_file == @file && @method.source_range.include?(@line)
end
set_file_and_dir_locals(@file)
def code?
!!code
rescue MethodSource::SourceNotFoundError
false
end
output.puts "\n#{text.bold('From:')} #{location}:\n\n"
output.puts code.with_line_numbers.with_marker(@line)
output.puts
end
private
def show_method?
args.empty? && @method && @method.source? && @method.source_range.count < 20 &&
# These checks are needed in case of an eval with a binding and file/line
# numbers set to outside the function. As in rails' use of ERB.
@method.source_file == @file && @method.source_range.include?(@line)
end
def code?
!!code
rescue MethodSource::SourceNotFoundError
false
end
def window_size
if args.empty?
Pry.config.default_window_size
else
args.first.to_i
end
end
def window_size
if args.empty?
Pry.config.default_window_size
else
args.first.to_i
end
end
end

View file

@ -0,0 +1,36 @@
class Pry
Pry::Commands.create_command(/wtf([?!]*)/, "Show the backtrace of the most recent exception") do
options :listing => 'wtf?'
banner <<-BANNER
Show's 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:
e.g.
pry(main)> wtf?
pry(main)> wtf?!???!?!?
To see the entire backtrace, pass the -v/--verbose flag:
e.g.
pry(main)> wtf -v
BANNER
def options(opt)
opt.on(:v, :verbose, "Show the full backtrace.")
end
def process
raise Pry::CommandError, "No most-recent exception" unless _pry_.last_exception
output.puts "#{text.bold('Exception:')} #{_pry_.last_exception.class}: #{_pry_.last_exception}\n--"
if opts.verbose?
output.puts Pry::Code.new(_pry_.last_exception.backtrace, 0, :text).with_line_numbers.to_s
else
output.puts Pry::Code.new(_pry_.last_exception.backtrace.first([captures[0].size, 0.5].max * 10), 0, :text).with_line_numbers.to_s
end
end
end
end

View file

@ -2,3 +2,4 @@ require "pry/helpers/base_helpers"
require "pry/helpers/options_helpers"
require "pry/helpers/command_helpers"
require "pry/helpers/text"
require "pry/helpers/module_introspection_helpers"

View file

@ -0,0 +1,106 @@
class Pry
module Helpers
module ModuleIntrospectionHelpers
attr_accessor :module_object
def module_object
if @module_object
@module_object
else
name = args.first
@module_object = WrappedModule.from_str(name, target)
if @module_object
sup = @module_object.ancestors.select do |anc|
anc.class == @module_object.wrapped.class
end[opts[:super]]
@module_object = sup ? Pry::WrappedModule(sup) : nil
end
end
end
# @param [String]
# @param [Binding] target The binding context of the input.
# @return [Symbol] type of input
def input_type(input,target)
if input == ""
:blank
elsif target.eval("defined? #{input} ") =~ /variable|constant/ &&
target.eval(input).respond_to?(:source_location)
:sourcable_object
elsif Pry::Method.from_str(input,target)
:method
elsif Pry::WrappedModule.from_str(input, target)
:module
else
:unknown
end
end
def process(name)
input = args.join(" ")
type = input_type(input, target)
code_or_doc = case type
when :blank
process_blank
when :sourcable_object
process_sourcable_object
when :method
process_method
when :module
process_module
else
command_error("method or module for '#{input}' could not be found or derived", false)
end
render_output(code_or_doc, opts)
end
def process_blank
if mod = extract_module_from_internal_binding
@module_object = mod
process_module
elsif meth = extract_method_from_binding
@method_object = meth
process_method
else
command_error("method or module for '' could not be derived", false)
end
end
def extract_module_from_internal_binding
if args.empty? && internal_binding?(target)
mod = target_self.is_a?(Module) ? target_self : target_self.class
Pry::WrappedModule(mod)
end
end
def extract_method_from_binding
Pry::Method.from_binding(target)
end
def module_start_line(mod, candidate_rank=0)
if opts.present?(:'base-one')
1
else
mod.candidate(candidate_rank).line
end
end
def use_line_numbers?
opts.present?(:b) || opts.present?(:l)
end
def attempt
rank = 0
begin
yield(rank)
rescue Pry::CommandError
raise if rank > (module_object.number_of_candidates - 1)
rank += 1
retry
end
end
end
end
end

View file

@ -4,7 +4,7 @@ describe "Pry::Command" do
before do
@set = Pry::CommandSet.new
@set.import Pry::DefaultCommands::Help
@set.import Pry::Commands
end
describe 'call_safely' do

View file

@ -546,7 +546,9 @@ describe "commands" do
end
it 'should change description of a command using desc' do
klass = Pry::CommandSet.new do; import Pry::DefaultCommands::Help; end
klass = Pry::CommandSet.new do
import Pry::Commands
end
orig = klass.commands["help"].description
klass.instance_eval do
desc "help", "blah"

View file

@ -2,7 +2,10 @@ require 'helper'
describe Pry::CommandSet do
before do
@set = Pry::CommandSet.new{ import Pry::DefaultCommands::Help }
@set = Pry::CommandSet.new do
import Pry::Commands
end
@ctx = {
:target => binding,
:command_set => @set

View file

@ -4,8 +4,7 @@ describe "'help' command" do
before do
@oldset = Pry.config.commands
@set = Pry.config.commands = Pry::CommandSet.new do
import Pry::DefaultCommands::Help
import Pry::DefaultCommands::Ls
import Pry::Commands
end
end