refactor `cat`

This commit is contained in:
Ryan Fitzgerald 2012-01-07 22:01:15 -08:00
parent 95061e306c
commit e7fed21ded
7 changed files with 167 additions and 138 deletions

View File

@ -1,4 +1,17 @@
class Pry
class << self
def Code(obj)
case obj
when Code
obj
when ::Method, Pry::Method, UnboundMethod, Proc
Code.from_method(obj)
else
Code.new(obj)
end
end
end
class Code
class << self
# Instantiate a `Code` object containing code loaded from a file or
@ -13,7 +26,7 @@ class Pry
if File.readable?(fn)
f = File.open(fn, 'r')
else
raise CommandError, "Cannot open #{file.inspect} for reading."
raise CommandError, "Cannot open #{fn.inspect} for reading."
end
end
new(f, 1, code_type)
@ -34,6 +47,8 @@ class Pry
end
end
attr_accessor :code_type
# @param [Array<String>] lines
# @param [Fixnum?] (1) start_line
# @param [Symbol?] (:ruby) code_type
@ -57,6 +72,8 @@ class Pry
alias << push
def before(line_num, lines=1)
return self unless line_num
dup.instance_eval do
@lines = @lines.select { |l, ln| ln >= line_num - lines && ln < line_num }
self
@ -64,13 +81,19 @@ class Pry
end
def between(start_line, end_line)
return self unless start_line && end_line
start_line -= 1 unless start_line < 0
end_line -= 1 unless end_line < 0
dup.instance_eval do
@lines = @lines[(start_line + 1)..(end_line + 1)]
@lines = @lines[start_line..end_line] || []
self
end
end
def around(line_num, lines=1)
return self unless line_num
dup.instance_eval do
@lines = @lines.select { |l, ln| ln >= line_num - lines && ln <= line_num + lines }
self
@ -78,6 +101,8 @@ class Pry
end
def after(line_num, lines=1)
return self unless line_num
dup.instance_eval do
@lines = @lines.select { |l, ln| ln > line_num && ln <= line_num + lines }
self
@ -101,6 +126,14 @@ class Pry
end
end
def with_indentation(spaces=0)
dup.instance_eval do
@with_indentation = !!spaces
@indentation_num = spaces
self
end
end
# @return [String]
def inspect
Object.instance_method(:to_s).bind(self).call
@ -134,7 +167,13 @@ class Pry
end
end
lines.map(&:first).join("\n")
if @with_indentation
lines.each do |l|
l[0] = "#{' ' * @indentation_num}#{l[0]}"
end
end
lines.map { |l| "#{l.first}\n" }.join
end
def method_missing(name, *args, &blk)

View File

@ -28,67 +28,93 @@ class Pry
_pry_.instance_eval(&Pry::FILE_COMPLETIONS)
end
end
alias_command "file-mode", "shell-mode"
command "cat", "Show code from a file or Pry's input buffer. Type `cat --help` for more information." do |*args|
start_line = 0
end_line = -1
file_name = nil
bt_index = 0
command_class "cat", "Show code from a file or Pry's input buffer. Type `cat --help` for more information." do
banner <<-USAGE
Usage: cat FILE
cat --ex [STACK_INDEX]
cat --in [INPUT_INDEX_OR_RANGE]
opts = Slop.parse!(args) do |opt|
opt.on :s, :start, "Start line (defaults to start of file)Line 1 is the first line.", true, :as => Integer do |line|
start_line = line - 1
end
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.
opt.on :e, :end, "End line (defaults to end of file). Line -1 is the last line", true, :as => Integer do |line|
end_line = line - 1
end
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
opt.on :ex, "Show a window of N lines either side of the last exception (defaults to 5).", :optional => true, :as => Integer do |bt_index_arg|
window_size = Pry.config.exception_window_size || 5
ex = _pry_.last_exception
next if !ex
if bt_index_arg
bt_index = bt_index_arg
else
bt_index = ex.bt_index
end
ex.bt_index = (bt_index + 1) % ex.backtrace.size
def options(opt)
opt.on :ex, "Show the context of the last exception.", :optional => true, :as => Integer
opt.on :i, :in, "Show one or more entries from Pry's expression history.", :optional => true, :as => Range, :default => -5..-1
ex_file, ex_line = ex.bt_source_location_for(bt_index)
start_line = (ex_line - 1) - window_size
start_line = start_line < 0 ? 0 : start_line
end_line = (ex_line - 1) + window_size
if ex_file && RbxPath.is_core_path?(ex_file)
file_name = RbxPath.convert_path_to_full(ex_file)
else
file_name = ex_file
end
end
opt.on :s, :start, "Starting line (defaults to the first line).", :optional => true, :as => Integer
opt.on :e, :end, "Ending line (defaults to the last line).", :optional => 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').", true, :as => Symbol
opt.on :i, :in, "Show entries from Pry's input expression history. Takes an index or range.", :optional => true, :as => Range, :default => -5..-1
opt.on :l, "line-numbers", "Show line numbers."
opt.on :t, :type, "The specific file type for syntax higlighting (e.g ruby, python)", true, :as => Symbol
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
def process
handler = case
when opts.present?(:ex)
method :process_ex
when opts.present?(:in)
method :process_in
else
method :process_file
end
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
end
next if opts.present?(:help)
def process_ex
window_size = Pry.config.exception_window_size || 5
ex = _pry_.last_exception
if opts.present?(:ex)
if file_name.nil?
raise CommandError, "No Exception or Exception has no associated file."
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]
end
else
file_name = args.shift
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
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))
render_output("#{header}#{code}", opts)
end
if opts.present?(:in)
def process_in
normalized_range = absolute_index_range(opts[:i], _pry_.input_array.length)
input_items = _pry_.input_array[normalized_range] || []
@ -98,66 +124,30 @@ class Pry
raise CommandError, "No expressions found."
end
if opts[:i].is_a?(Range)
if zipped_items.length > 1
contents = ""
zipped_items.each do |i, s|
contents << "#{text.bold(i.to_s)}:\n"
code = syntax_highlight_by_file_type_or_specified(s, nil, :ruby)
if opts.present?(:'line-numbers')
contents << text.indent(text.with_line_numbers(code, 1), 2)
else
contents << text.indent(code, 2)
end
contents << yield(Pry::Code(s).with_indentation(2)).to_s
end
else
contents = syntax_highlight_by_file_type_or_specified(zipped_items.first.last, nil, :ruby)
contents = yield(Pry::Code(zipped_items.first.last))
end
else
render_output(contents, opts)
end
def process_file
file_name = args.shift
unless file_name
raise CommandError, "Must provide a file name."
raise CommandError, "Must provide a filename, --in, or --ex."
end
begin
contents, _, _ = read_between_the_lines(file_name, start_line, end_line)
rescue Errno::ENOENT
raise CommandError, "Could not find file: #{file_name}"
end
code = yield(Pry::Code.from_file(file_name))
contents = syntax_highlight_by_file_type_or_specified(contents, file_name, opts[:type])
if opts.present?(:'line-numbers')
contents = text.with_line_numbers contents, start_line + 1
end
end
# add the arrow pointing to line that caused the exception
if opts.present?(:ex)
ex_file, ex_line = _pry_.last_exception.bt_source_location_for(bt_index)
contents = text.with_line_numbers contents, start_line + 1, :bright_red
contents = contents.lines.each_with_index.map do |line, idx|
l = idx + start_line
if l == (ex_line - 1)
" =>#{line}"
else
" #{line}"
end
end.join
# header for exceptions
output.puts "\n#{Pry::Helpers::Text.bold('Exception:')} #{_pry_.last_exception.class}: #{_pry_.last_exception.message}\n--"
output.puts "#{Pry::Helpers::Text.bold('From:')} #{ex_file} @ line #{ex_line} @ #{text.bold('level: ')} #{bt_index} of backtrace (of #{_pry_.last_exception.backtrace.size - 1}).\n\n"
end
set_file_and_dir_locals(file_name)
if opts.present?(:flood)
output.puts contents
else
stagger_output(contents)
set_file_and_dir_locals(file_name)
render_output(code, opts)
end
end
end

View File

@ -15,6 +15,14 @@ class Pry
file.close(opts[:unlink])
end
def render_output(str, opts={})
if opts[:flood]
output.puts str
else
stagger_output str
end
end
def get_method_or_raise(name, target, opts={}, omit_help=false)
meth = Pry::Method.from_str(name, target, opts)

View File

@ -15,10 +15,11 @@ class Pry
attr_accessor :binding_stack
attr_accessor :last_result
attr_accessor :last_exception
attr_accessor :last_file
attr_accessor :last_dir
attr_reader :last_exception
attr_reader :input_array
attr_reader :output_array
@ -228,7 +229,8 @@ class Pry
result = set_last_result(target.eval(code, Pry.eval_path, Pry.current_line), target)
result
rescue RescuableException => e
result = set_last_exception(e, target)
self.last_exception = e
e
ensure
update_input_history(code)
hooks.exec_hook :after_eval, result, self
@ -406,16 +408,18 @@ class Pry
end
# Set the last exception for a session.
# This method should not need to be invoked directly.
# @param [Exception] ex The exception.
# @param [Binding] target The binding to set `_ex_` on.
def set_last_exception(ex, target)
# @param [Exception] ex
def last_exception=(ex)
class << ex
attr_accessor :file, :line, :bt_index
def bt_source_location_for(index)
backtrace[index] =~ /(.*):(\d+)/
[$1, $2.to_i]
end
def inc_bt_index
@bt_index = (@bt_index + 1) % backtrace.size
end
end
ex.bt_index = 0
@ -423,8 +427,7 @@ class Pry
@last_result_is_exception = true
@output_array << ex
self.last_exception = ex
@last_exception = ex
end
# Update Pry's internal state after evalling code.

View File

@ -30,23 +30,12 @@ class << Pry
end
end
class MockPryException
attr_accessor :bt_index
attr_accessor :backtrace
def initialize(*backtrace)
@backtrace = backtrace
@bt_index = 0
end
def message
"mock exception"
end
def bt_source_location_for(index)
backtrace[index] =~ /(.*):(\d+)/
[$1, $2.to_i]
def mock_exception(*mock_backtrace)
e = StandardError.new("mock exception")
(class << e; self; end).class_eval do
define_method(:backtrace) { mock_backtrace }
end
e
end
Pry.reset_defaults

View File

@ -133,7 +133,7 @@ describe "Pry::DefaultCommands::Introspection" do
it 'should start editor on first level of backtrace when --ex used with no argument ' do
pry_instance = Pry.new(:input => StringIO.new("edit -n --ex"), :output => StringIO.new)
pry_instance.last_exception = MockPryException.new("a:1", "b:2", "c:3")
pry_instance.last_exception = mock_exception("a:1", "b:2", "c:3")
pry_instance.rep(self)
@__ex_file__.should == "a"
@__ex_line__.should == 1
@ -141,7 +141,7 @@ describe "Pry::DefaultCommands::Introspection" do
it 'should start editor on first level of backtrace when --ex 0 used ' do
pry_instance = Pry.new(:input => StringIO.new("edit -n --ex 0"), :output => StringIO.new)
pry_instance.last_exception = MockPryException.new("a:1", "b:2", "c:3")
pry_instance.last_exception = mock_exception("a:1", "b:2", "c:3")
pry_instance.rep(self)
@__ex_file__.should == "a"
@__ex_line__.should == 1
@ -149,7 +149,7 @@ describe "Pry::DefaultCommands::Introspection" do
it 'should start editor on second level of backtrace when --ex 1 used' do
pry_instance = Pry.new(:input => StringIO.new("edit -n --ex 1"), :output => StringIO.new)
pry_instance.last_exception = MockPryException.new("a:1", "b:2", "c:3")
pry_instance.last_exception = mock_exception("a:1", "b:2", "c:3")
pry_instance.rep(self)
@__ex_file__.should == "b"
@__ex_line__.should == 2
@ -157,7 +157,7 @@ describe "Pry::DefaultCommands::Introspection" do
it 'should start editor on third level of backtrace when --ex 2 used' do
pry_instance = Pry.new(:input => StringIO.new("edit -n --ex 2"), :output => StringIO.new)
pry_instance.last_exception = MockPryException.new("a:1", "b:2", "c:3")
pry_instance.last_exception = mock_exception("a:1", "b:2", "c:3")
pry_instance.rep(self)
@__ex_file__.should == "c"
@__ex_line__.should == 3
@ -165,7 +165,7 @@ describe "Pry::DefaultCommands::Introspection" do
it 'should display error message when backtrace level is out of bounds (using --ex 4)' do
pry_instance = Pry.new(:input => StringIO.new("edit -n --ex 4"), :output => str_output = StringIO.new)
pry_instance.last_exception = MockPryException.new("a:1", "b:2", "c:3")
pry_instance.last_exception = mock_exception("a:1", "b:2", "c:3")
pry_instance.rep(self)
str_output.string.should =~ /Exception has no associated file/
end

View File

@ -5,7 +5,7 @@ describe "Pry::DefaultCommands::Shell" do
describe "on receiving a file that does not exist" do
it 'should display an error message' do
mock_pry("cat supercalifragilicious66").should =~ /Could not find file/
mock_pry("cat supercalifragilicious66").should =~ /Cannot open/
end
end
@ -65,7 +65,7 @@ describe "Pry::DefaultCommands::Shell" do
temp_file do |f|
f << "bt number 1"
f.flush
pry_instance.last_exception = MockPryException.new("#{f.path}:1", "x", "x")
pry_instance.last_exception = mock_exception("#{f.path}:1", "x", "x")
pry_instance.rep(self)
end
@ -78,7 +78,7 @@ describe "Pry::DefaultCommands::Shell" do
temp_file do |f|
f << "bt number 1"
f.flush
pry_instance.last_exception = MockPryException.new("#{f.path}:1", "x", "x")
pry_instance.last_exception = mock_exception("#{f.path}:1", "x", "x")
pry_instance.rep(self)
end
@ -91,31 +91,31 @@ describe "Pry::DefaultCommands::Shell" do
temp_file do |f|
f << "bt number 2"
f.flush
pry_instance.last_exception = MockPryException.new("x", "#{f.path}:1", "x")
pry_instance.last_exception = mock_exception("x", "#{f.path}:1", "x")
pry_instance.rep(self)
end
str_output.string.should =~ /bt number 2/
end
it 'should cat third level of backtrace when --ex 2 used ' do
it 'should cat third level of backtrace when --ex 2 used' do
pry_instance = Pry.new(:input => StringIO.new("cat --ex 2"), :output => str_output = StringIO.new)
temp_file do |f|
f << "bt number 3"
f.flush
pry_instance.last_exception = MockPryException.new("x", "x", "#{f.path}:1")
pry_instance.last_exception = mock_exception("x", "x", "#{f.path}:1")
pry_instance.rep(self)
end
str_output.string.should =~ /bt number 3/
end
it 'should show error when backtrace level out of bounds ' do
it 'should show error when backtrace level out of bounds' do
pry_instance = Pry.new(:input => StringIO.new("cat --ex 3"), :output => str_output = StringIO.new)
pry_instance.last_exception = MockPryException.new("x", "x", "x")
pry_instance.last_exception = mock_exception("x", "x", "x")
pry_instance.rep(self)
str_output.string.should =~ /No Exception or Exception has no associated file/
str_output.string.should =~ /out of bounds/
end
it 'each successive cat --ex should show the next level of backtrace, and going past the final level should return to the first' do
@ -129,7 +129,7 @@ describe "Pry::DefaultCommands::Shell" do
pry_instance = Pry.new(:input => StringIO.new("cat --ex\n" * 4),
:output => (str_output = StringIO.new))
pry_instance.last_exception = MockPryException.new(*temp_files.map { |f| "#{f.path}:1" })
pry_instance.last_exception = mock_exception(*temp_files.map { |f| "#{f.path}:1" })
3.times do |i|
pry_instance.rep(self)