mirror of
https://github.com/pry/pry.git
synced 2022-11-09 12:35:05 -05:00
Merge remote-tracking branch 'origin/fix_inspect_output'
Conflicts: lib/pry/pager.rb
This commit is contained in:
commit
4e2c51077f
7 changed files with 302 additions and 96 deletions
38
lib/pry.rb
38
lib/pry.rb
|
@ -20,41 +20,10 @@ class Pry
|
||||||
|
|
||||||
# The default print
|
# The default print
|
||||||
DEFAULT_PRINT = proc do |output, value|
|
DEFAULT_PRINT = proc do |output, value|
|
||||||
output_with_default_format(output, value, :hashrocket => true)
|
Pry::Pager.with_pager(output) do |pager|
|
||||||
end
|
pager.print "=> "
|
||||||
|
Pry::ColorPrinter.pp(value, pager, Pry::Terminal.width! - 1)
|
||||||
def self.output_with_default_format(output, value, options = {})
|
|
||||||
stringified = begin
|
|
||||||
value.pretty_inspect
|
|
||||||
rescue RescuableException
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
unless String === stringified
|
|
||||||
# Read the class name off of the singleton class to provide a default
|
|
||||||
# inspect.
|
|
||||||
eig = class << value; self; end
|
|
||||||
klass = Pry::Method.safe_send(eig, :ancestors).first
|
|
||||||
id = value.__id__.to_s(16) rescue 0
|
|
||||||
stringified = "#<#{klass}:0x#{id}>"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
nonce = SecureRandom.hex(4)
|
|
||||||
|
|
||||||
stringified.gsub!(/#</, "%<#{nonce}")
|
|
||||||
# Don't recolorize output with color (for cucumber, looksee, etc.) [Issue #751]
|
|
||||||
colorized = if stringified =~ /\e\[/
|
|
||||||
stringified
|
|
||||||
else
|
|
||||||
Helpers::BaseHelpers.colorize_code(stringified)
|
|
||||||
end
|
|
||||||
|
|
||||||
# avoid colour-leak from CodeRay and any of the users' previous output
|
|
||||||
colorized = colorized.sub(/(\n*)\z/, "\e[0m\\1") if Pry.color
|
|
||||||
|
|
||||||
result = colorized.gsub(/%<(.*?)#{nonce}/, '#<\1')
|
|
||||||
result = "=> #{result}" if options[:hashrocket]
|
|
||||||
Helpers::BaseHelpers.stagger_output(result, output)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# may be convenient when working with enormous objects and
|
# may be convenient when working with enormous objects and
|
||||||
|
@ -225,6 +194,7 @@ require 'pry/core_extensions'
|
||||||
require 'pry/pry_class'
|
require 'pry/pry_class'
|
||||||
require 'pry/pry_instance'
|
require 'pry/pry_instance'
|
||||||
require 'pry/cli'
|
require 'pry/cli'
|
||||||
|
require 'pry/color_printer'
|
||||||
require 'pry/pager'
|
require 'pry/pager'
|
||||||
require 'pry/terminal'
|
require 'pry/terminal'
|
||||||
require 'pry/editor'
|
require 'pry/editor'
|
||||||
|
|
54
lib/pry/color_printer.rb
Normal file
54
lib/pry/color_printer.rb
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
# PP subclass for streaming inspect output in color.
|
||||||
|
class Pry
|
||||||
|
class ColorPrinter < ::PP
|
||||||
|
OBJ_COLOR = begin
|
||||||
|
code = CodeRay::Encoders::Terminal::TOKEN_COLORS[:keyword]
|
||||||
|
if code.start_with? "\e"
|
||||||
|
code
|
||||||
|
else
|
||||||
|
"\e[0m\e[0;#{code}m"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.pp(obj, out = $>, width = 79)
|
||||||
|
q = ColorPrinter.new(out, width)
|
||||||
|
q.guard_inspect_key { q.pp obj }
|
||||||
|
q.flush
|
||||||
|
out << "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def text(str, width = str.length)
|
||||||
|
super *if !Pry.color
|
||||||
|
[str, width]
|
||||||
|
# Don't recolorize output with color [Issue #751]
|
||||||
|
elsif str.include?("\e[")
|
||||||
|
["#{str}\e[0m", width]
|
||||||
|
elsif str.start_with?('#<') || str == '=' || str == '>'
|
||||||
|
[highlight_object_literal(str), width]
|
||||||
|
else
|
||||||
|
[CodeRay.scan(str, :ruby).term, width]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def pp(obj)
|
||||||
|
super
|
||||||
|
rescue => e
|
||||||
|
raise if e.is_a? Pry::Pager::StopPaging
|
||||||
|
|
||||||
|
# Read the class name off of the singleton class to provide a default
|
||||||
|
# inspect.
|
||||||
|
eig = class << obj; self; end
|
||||||
|
klass = Pry::Method.safe_send(eig, :ancestors).first
|
||||||
|
obj_id = obj.__id__.to_s(16) rescue 0
|
||||||
|
str = "#<#{klass}:0x#{obj_id}>"
|
||||||
|
|
||||||
|
text(Pry.color ? highlight_object_literal(str) : str)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def highlight_object_literal(object_literal)
|
||||||
|
"#{OBJ_COLOR}#{object_literal}\e[0m"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -327,9 +327,7 @@ class Pry
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_value(value)
|
def format_value(value)
|
||||||
accumulator = StringIO.new
|
Pry::ColorPrinter.pp(value, "")
|
||||||
Pry.output_with_default_format(accumulator, value, :hashrocket => false)
|
|
||||||
accumulator.string
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add a new section to the output. Outputs nothing if the section would be empty.
|
# Add a new section to the output. Outputs nothing if the section would be empty.
|
||||||
|
|
|
@ -98,9 +98,8 @@ class Pry
|
||||||
mri? && RUBY_VERSION =~ /1.9/
|
mri? && RUBY_VERSION =~ /1.9/
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Send the given text through the best available pager (if Pry.pager is
|
||||||
# Try to use `less` for paging, if it fails then use
|
# enabled). Infers where to send the output if used as a mixin.
|
||||||
# simple_pager. Also do not page if Pry.pager is falsey
|
|
||||||
def stagger_output(text, out = nil)
|
def stagger_output(text, out = nil)
|
||||||
out ||= case
|
out ||= case
|
||||||
when respond_to?(:output)
|
when respond_to?(:output)
|
||||||
|
@ -114,14 +113,7 @@ class Pry
|
||||||
$stdout
|
$stdout
|
||||||
end
|
end
|
||||||
|
|
||||||
if text.lines.count < Pry::Pager.page_size || !Pry.pager
|
Pry::Pager.page(text, out)
|
||||||
out.puts text
|
|
||||||
else
|
|
||||||
Pry::Pager.page(text)
|
|
||||||
end
|
|
||||||
rescue Errno::ENOENT
|
|
||||||
Pry::Pager.page(text, :simple)
|
|
||||||
rescue Errno::EPIPE
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# @param [String] arg_string The object path expressed as a string.
|
# @param [String] arg_string The object path expressed as a string.
|
||||||
|
|
218
lib/pry/pager.rb
218
lib/pry/pager.rb
|
@ -1,64 +1,117 @@
|
||||||
require 'pry/terminal'
|
require 'pry/terminal'
|
||||||
class Pry::Pager
|
|
||||||
# @param [String] text
|
# A pager is an `IO`-like object that accepts text and either prints it
|
||||||
# A piece of text to run through a pager.
|
# immediately, prints it one page at a time, or streams it to an external
|
||||||
# @param [Symbol?] pager
|
# program to print one page at a time.
|
||||||
# `:simple` -- Use the pure ruby pager.
|
module Pry::Pager
|
||||||
# `:system` -- Use the system pager (less) or the environment variable
|
class StopPaging < StandardError
|
||||||
# $PAGER if set.
|
end
|
||||||
# `nil` -- Infer what pager to use from the environment. What this
|
|
||||||
# really means is that JRuby and systems that do not have
|
# Send the given text through the best available pager (if `Pry.pager` is
|
||||||
# access to 'less' will run through the pure ruby pager.
|
# enabled).
|
||||||
def self.page(text, pager = nil)
|
# @param [String] text A piece of text to run through a pager.
|
||||||
case pager
|
# @param [IO] output (`$stdout`) An object to send output to.
|
||||||
when nil
|
def self.page(text, output = $stdout)
|
||||||
no_pager = !SystemPager.available?
|
with_pager(output) do |pager|
|
||||||
if no_pager || Pry::Helpers::BaseHelpers.jruby?
|
pager << text
|
||||||
SimplePager.new(text).page
|
|
||||||
else
|
|
||||||
SystemPager.new(text).page
|
|
||||||
end
|
|
||||||
when :simple
|
|
||||||
SimplePager.new(text).page
|
|
||||||
when :system
|
|
||||||
SystemPager.new(text).page
|
|
||||||
else
|
|
||||||
raise "'#{pager}' is not a recognized pager."
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.page_size
|
# Yields a pager object (`NullPager`, `SimplePager`, or `SystemPager`). All
|
||||||
@page_size ||= Pry::Terminal.height!
|
# pagers accept output with `#puts`, `#print`, `#write`, and `#<<`.
|
||||||
|
# @param [IO] output (`$stdout`) An object to send output to.
|
||||||
|
def self.with_pager(output = $stdout)
|
||||||
|
pager = best_available(output)
|
||||||
|
yield pager
|
||||||
|
rescue StopPaging
|
||||||
|
ensure
|
||||||
|
pager.close if pager
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(text)
|
# Return an instance of the "best" available pager class -- `SystemPager` if
|
||||||
@text = text
|
# possible, `SimplePager` if `SystemPager` isn't available, and `NullPager`
|
||||||
|
# if the user has disabled paging. All pagers accept output with `#puts`,
|
||||||
|
# `#print`, `#write`, and `#<<`. You must call `#close` when you're done
|
||||||
|
# writing output to a pager, and you must rescue `Pry::Pager::StopPaging`.
|
||||||
|
# These requirements can be avoided by using `.with_pager` instead.
|
||||||
|
# @param [#<<] output ($stdout) An object to send output to.
|
||||||
|
def self.best_available(output)
|
||||||
|
if !Pry.pager
|
||||||
|
NullPager.new(output)
|
||||||
|
elsif !SystemPager.available? || Pry::Helpers::BaseHelpers.jruby?
|
||||||
|
SimplePager.new(output)
|
||||||
|
else
|
||||||
|
SystemPager.new(output)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class SimplePager < Pry::Pager
|
# `NullPager` is a "pager" that actually just prints all output as it comes
|
||||||
def page
|
# in. Used when `Pry.pager` is false.
|
||||||
# The pager size minus the number of lines used by the simple pager info bar.
|
class NullPager
|
||||||
page_size = Pry::Pager.page_size - 3
|
def initialize(out)
|
||||||
text_array = @text.lines.to_a
|
@out = out
|
||||||
|
end
|
||||||
|
|
||||||
text_array.each_slice(page_size) do |chunk|
|
def puts(str)
|
||||||
puts chunk.join
|
print "#{str.chomp}\n"
|
||||||
break if chunk.size < page_size
|
end
|
||||||
if text_array.size > page_size
|
|
||||||
puts "\n<page break> --- Press enter to continue ( q<enter> to break ) --- <page break>"
|
def print(str)
|
||||||
break if Readline.readline.chomp == "q"
|
write str
|
||||||
|
end
|
||||||
|
alias << print
|
||||||
|
|
||||||
|
def write(str)
|
||||||
|
@out.write str
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def height
|
||||||
|
@height ||= Pry::Terminal.height!
|
||||||
|
end
|
||||||
|
|
||||||
|
def width
|
||||||
|
@width ||= Pry::Terminal.width!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# `SimplePager` is a straightforward pure-Ruby pager. We use it on JRuby and
|
||||||
|
# when we can't find a usable external pager.
|
||||||
|
class SimplePager < NullPager
|
||||||
|
def initialize(*)
|
||||||
|
super
|
||||||
|
@tracker = PageTracker.new(height - 3, width)
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(str)
|
||||||
|
str.lines.each do |line|
|
||||||
|
@out.print line
|
||||||
|
@tracker.record line
|
||||||
|
|
||||||
|
if @tracker.page?
|
||||||
|
@out.puts "\n<page break> --- Press enter to continue " \
|
||||||
|
"( q<enter> to break ) --- <page break>"
|
||||||
|
raise StopPaging if Readline.readline.chomp == "q"
|
||||||
|
@tracker.reset
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class SystemPager < Pry::Pager
|
# `SystemPager` buffers output until we're pretty sure it's at least a page
|
||||||
|
# long, then invokes an external pager and starts streaming output to it. If
|
||||||
|
# `#close` is called before then, it just prints out the buffered content.
|
||||||
|
class SystemPager < NullPager
|
||||||
def self.default_pager
|
def self.default_pager
|
||||||
pager = ENV["PAGER"] || ""
|
pager = ENV["PAGER"] || ""
|
||||||
|
|
||||||
# Default to less, and make sure less is being passed the correct options
|
# Default to less, and make sure less is being passed the correct options
|
||||||
if pager.strip.empty? or pager =~ /^less\s*/
|
if pager.strip.empty? or pager =~ /^less\b/
|
||||||
pager = "less -R -S -F -X"
|
pager = "less -R -F -X"
|
||||||
end
|
end
|
||||||
|
|
||||||
pager
|
pager
|
||||||
|
@ -79,13 +132,84 @@ class Pry::Pager
|
||||||
|
|
||||||
def initialize(*)
|
def initialize(*)
|
||||||
super
|
super
|
||||||
@pager = SystemPager.default_pager
|
@tracker = PageTracker.new(height, width)
|
||||||
|
@buffer = ""
|
||||||
end
|
end
|
||||||
|
|
||||||
def page
|
def write(str)
|
||||||
IO.popen(@pager, 'w') do |io|
|
if invoked_pager?
|
||||||
io.write @text
|
pager.write str
|
||||||
|
else
|
||||||
|
@tracker.record str
|
||||||
|
@buffer << str
|
||||||
|
|
||||||
|
if @tracker.page?
|
||||||
|
pager.write @buffer
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
rescue Errno::EPIPE
|
||||||
|
raise StopPaging
|
||||||
|
end
|
||||||
|
|
||||||
|
def close
|
||||||
|
if invoked_pager?
|
||||||
|
pager.close
|
||||||
|
else
|
||||||
|
@out.puts @buffer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def invoked_pager?
|
||||||
|
@pager
|
||||||
|
end
|
||||||
|
|
||||||
|
def pager
|
||||||
|
@pager ||= IO.popen(self.class.default_pager, 'w')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# `PageTracker` tracks output to determine whether it's likely to take up a
|
||||||
|
# whole page. This doesn't need to be super precise, but we can use it for
|
||||||
|
# `SimplePager` and to avoid invoking the system pager unnecessarily.
|
||||||
|
#
|
||||||
|
# One simplifying assumption is that we don't need `#page?` to return `true`
|
||||||
|
# on the basis of an incomplete line. Long lines should be counted as
|
||||||
|
# multiple lines, but we don't have to transition from `false` to `true`
|
||||||
|
# until we see a newline.
|
||||||
|
class PageTracker
|
||||||
|
def initialize(rows, cols)
|
||||||
|
@rows, @cols = rows, cols
|
||||||
|
reset
|
||||||
|
end
|
||||||
|
|
||||||
|
def record(str)
|
||||||
|
str.lines.each do |line|
|
||||||
|
if line.end_with? "\n"
|
||||||
|
@row += ((@col + line_length(line) - 1) / @cols) + 1
|
||||||
|
@col = 0
|
||||||
|
else
|
||||||
|
@col += line_length(line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def page?
|
||||||
|
@row >= @rows
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset
|
||||||
|
@row = 0
|
||||||
|
@col = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Approximation of the printable length of a given line, without the
|
||||||
|
# newline and without ANSI color codes.
|
||||||
|
def line_length(line)
|
||||||
|
line.chomp.gsub(/\e\[[\d;]*m/, '').length
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
67
spec/pager_spec.rb
Normal file
67
spec/pager_spec.rb
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
require "helper"
|
||||||
|
|
||||||
|
describe "Pry::Pager" do
|
||||||
|
describe "PageTracker" do
|
||||||
|
before do
|
||||||
|
@pt = Pry::Pager::PageTracker.new(10, 10)
|
||||||
|
end
|
||||||
|
|
||||||
|
def record_short_line
|
||||||
|
@pt.record "012345678\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def record_long_line
|
||||||
|
@pt.record "0123456789012\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def record_multiline
|
||||||
|
@pt.record "0123456789012\n01\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
def record_string_without_newline
|
||||||
|
@pt.record "0123456789"
|
||||||
|
end
|
||||||
|
|
||||||
|
def record_string_with_color_codes
|
||||||
|
@pt.record(CodeRay.scan("0123456789", :ruby).term + "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "records short lines that don't add up to a page" do
|
||||||
|
9.times { record_short_line }
|
||||||
|
@pt.page?.should.be.false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "records short lines that do add up to a page" do
|
||||||
|
10.times { record_short_line }
|
||||||
|
@pt.page?.should.be.true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "treats a long line as taking up more than one row" do
|
||||||
|
4.times { record_long_line }
|
||||||
|
@pt.page?.should.be.false
|
||||||
|
record_long_line
|
||||||
|
@pt.page?.should.be.true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "records a string with an embedded newline" do
|
||||||
|
3.times { record_multiline }
|
||||||
|
@pt.page?.should.be.false
|
||||||
|
record_short_line
|
||||||
|
@pt.page?.should.be.true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't count a line until it ends" do
|
||||||
|
12.times { record_string_without_newline }
|
||||||
|
@pt.page?.should.be.false
|
||||||
|
record_short_line
|
||||||
|
@pt.page?.should.be.true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't count ansi color codes towards length" do
|
||||||
|
9.times { record_string_with_color_codes }
|
||||||
|
@pt.page?.should.be.false
|
||||||
|
record_string_with_color_codes
|
||||||
|
@pt.page?.should.be.true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -60,8 +60,9 @@ describe Pry do
|
||||||
|
|
||||||
it "should colorize strings as though they were ruby" do
|
it "should colorize strings as though they were ruby" do
|
||||||
accumulator = StringIO.new
|
accumulator = StringIO.new
|
||||||
|
colorized = CodeRay.scan("[1]", :ruby).term
|
||||||
Pry.config.print.call(accumulator, [1])
|
Pry.config.print.call(accumulator, [1])
|
||||||
accumulator.string.should == "=> [\e[1;34m1\e[0m]\e[0m\n"
|
accumulator.string.should == "=> #{colorized}\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not colorize strings that already include color" do
|
it "should not colorize strings that already include color" do
|
||||||
|
|
Loading…
Reference in a new issue