2012-02-23 03:28:00 +00:00
require 'tempfile'
2012-03-02 01:04:28 +00:00
require 'pry/default_commands/hist'
2012-02-23 03:28:00 +00:00
class Pry
module DefaultCommands
Editing = Pry :: CommandSet . new do
2012-03-02 01:04:28 +00:00
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
2012-02-23 03:28:00 +00:00
def process
output . puts " Input buffer cleared! "
2012-06-16 19:59:14 +00:00
eval_string . replace ( " " )
2012-02-23 03:28:00 +00:00
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 )
2012-05-27 08:36:41 +00:00
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 line of _in_. N may be a range. " , :optional_argument = > true , :as = > Range , :default = > - 1 .. - 1
2012-02-23 03:28:00 +00:00
opt . on :t , :temp , " Open an empty temporary file "
2012-05-27 08:36:41 +00:00
opt . on :l , :line , " Jump to this line in the opened file " , :argument = > true , :as = > Integer
2012-02-23 03:28:00 +00:00
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
2012-04-04 02:48:09 +00:00
reload = ! opts . present? ( :'no-reload' ) && ! Pry . config . disable_auto_reload
2012-05-20 06:49:30 +00:00
f . close ( false )
2012-04-04 02:48:09 +00:00
invoke_editor ( f . path , line , reload )
if reload
2012-02-23 03:28:00 +00:00
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 )
2012-04-04 02:48:09 +00:00
reload = opts . present? ( :reload ) || ( ( opts . present? ( :ex ) || file_name . end_with? ( " .rb " ) ) && ! opts . present? ( :'no-reload' ) ) && ! Pry . config . disable_auto_reload
invoke_editor ( file_name , line , reload )
2012-02-23 03:28:00 +00:00
set_file_and_dir_locals ( file_name )
2012-04-04 02:48:09 +00:00
if reload
2012-02-23 03:28:00 +00:00
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! \n Ensure that #{ text . bold ( " Pry.config.editor " ) } is set to your editor of choice. "
end
begin
@method = method_object
rescue NonMethodContextError = > 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
2012-03-31 21:05:21 +00:00
lines [ 0 ] = definition_line_for_owner ( lines [ 0 ] )
2012-02-23 03:28:00 +00:00
temp_file do | f |
f . puts lines . join
f . flush
2012-05-20 06:49:30 +00:00
f . close ( false )
2012-04-04 02:48:09 +00:00
invoke_editor ( f . path , 0 , true )
2012-02-23 03:28:00 +00:00
if @method . alias?
with_method_transaction ( original_name , @method . owner ) do
Pry . new ( :input = > StringIO . new ( File . read ( f . path ) ) ) . rep ( @method . owner )
Pry . binding_for ( @method . owner ) . eval ( " alias #{ @method . name } #{ original_name } " )
end
else
Pry . new ( :input = > StringIO . new ( File . read ( f . path ) ) ) . rep ( @method . owner )
end
end
end
def process_file
file , line = extract_file_and_line
2012-04-04 02:48:09 +00:00
reload = ! opts . present? ( :'no-reload' ) && ! Pry . config . disable_auto_reload
invoke_editor ( file , opts [ " no-jump " ] ? 0 : line , reload )
2012-02-23 03:28:00 +00:00
silence_warnings do
2012-04-04 02:48:09 +00:00
load file if reload
2012-02-23 03:28:00 +00:00
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
def with_method_transaction ( meth_name , target = TOPLEVEL_BINDING )
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
2012-03-31 21:05:21 +00:00
# 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
2012-02-23 03:28:00 +00:00
end
create_command ( / amend-line(?: (-? \ d+)(?: \ . \ .(-? \ d+))?)? / ) do
description " Amend a line of input in multi-line mode. "
2012-06-16 07:10:35 +00:00
command_options :interpolate = > false , :listing = > " amend-line "
2012-02-23 03:28:00 +00:00
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
2012-04-12 14:17:47 +00:00
include Helpers :: DocumentationHelpers
2012-02-23 03:28:00 +00:00
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 )
2012-05-27 08:36:41 +00:00
opt . on :m , :method , " Play a method's source. " , :argument = > true do | meth_name |
2012-02-23 03:28:00 +00:00
meth = get_method_or_raise ( meth_name , target , { } )
self . content << meth . source
end
2012-05-27 08:36:41 +00:00
opt . on :d , :doc , " Play a method's documentation. " , :argument = > true do | meth_name |
2012-02-23 03:28:00 +00:00
meth = get_method_or_raise ( meth_name , target , { } )
text . no_color do
self . content << process_comment_markup ( meth . doc , :ruby )
end
end
2012-05-27 08:36:41 +00:00
opt . on :c , :command , " Play a command's source. " , :argument = > true do | command_name |
2012-02-23 03:28:00 +00:00
command = find_command ( command_name )
2012-05-28 23:37:02 +00:00
block = Pry :: Method . new ( command . block )
2012-02-23 03:28:00 +00:00
self . content << block . source
end
2012-05-27 08:36:41 +00:00
opt . on :f , :file , " Play a file. " , :argument = > true do | file |
2012-02-23 03:28:00 +00:00
self . content << File . read ( File . expand_path ( file ) )
end
2012-05-27 08:36:41 +00:00
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 ,
2012-02-23 03:28:00 +00:00
: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
2012-04-10 13:39:29 +00:00
run " show-input " unless Pry :: Code . complete_expression? ( eval_string )
2012-02-23 03:28:00 +00:00
end
def process_non_opt
args . each do | arg |
begin
self . content << target . eval ( arg )
rescue Pry :: RescuableException
raise CommandError , " Prblem 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