refactor cat command

Put each output formatter (input expression, exception, file) into its own class.
This commit is contained in:
John Mair 2013-01-06 21:44:33 +01:00
parent d77bfe285c
commit 92fe82dbd6
5 changed files with 245 additions and 141 deletions

View File

@ -1,5 +1,10 @@
class Pry
class Command::Cat < Pry::ClassCommand
require 'pry/commands/cat/abstract_formatter.rb'
require 'pry/commands/cat/input_expression_formatter.rb'
require 'pry/commands/cat/exception_formatter.rb'
require 'pry/commands/cat/file_formatter.rb'
match 'cat'
group 'Input and Output'
description "Show code from a file, Pry's input buffer, or the last " \
@ -21,160 +26,28 @@ class Pry
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 = case
when opts.present?(:ex)
ExceptionFormatter.new(_pry_.last_exception, _pry_, opts).format
when opts.present?(:in)
InputExpressionFormatter.new(_pry_.input_array, opts).format
else
FileFormatter.new(args.first, _pry_, opts).format
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 = absolute_index_number(opts[:ex], ex.backtrace.size)
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
stagger_output(output)
end
def complete(search)
super + Bond::Rc.files(search.split(" ").last || '')
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", "pryrc", "irbrc"
: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
Pry::Commands.add_command(Pry::Command::Cat)

View File

@ -0,0 +1,27 @@
class Pry
class Command::Cat
class AbstractFormatter
include Pry::Helpers::CommandHelpers
include Pry::Helpers::BaseHelpers
private
def decorate(content)
content.code_type = code_type
content.between(*between_lines).
with_line_numbers(use_line_numbers?)
end
def code_type
opts[:type] || :ruby
end
def use_line_numbers?
opts.present?(:'line-numbers') || opts.present?(:ex)
end
def between_lines
[opts[:start] || 1, opts[:end] || -1]
end
end
end
end

View File

@ -0,0 +1,78 @@
class Pry
class Command::Cat
class ExceptionFormatter < AbstractFormatter
attr_accessor :ex
attr_accessor :opts
attr_accessor :_pry_
def initialize(exception, _pry_, opts)
@ex = exception
@opts = opts
@_pry_ = _pry_
end
def format
check_for_errors
set_file_and_dir_locals(backtrace_file, _pry_, _pry_.current_context)
code = decorate(Pry::Code.from_file(backtrace_file).
between(*start_and_end_line_for_code_window).
with_marker(backtrace_line)).to_s
"#{header}#{code}"
end
private
def code_window_size
Pry.config.default_window_size || 5
end
def backtrace_level
return @backtrace_level if @backtrace_level
bl = if opts[:ex].nil?
ex.bt_index
else
ex.bt_index = absolute_index_number(opts[:ex], ex.backtrace.size)
end
increment_backtrace_level
@backtrace_level = bl
end
def increment_backtrace_level
ex.inc_bt_index
end
def backtrace_file
file = Array(ex.bt_source_location_for(backtrace_level)).first
(file && RbxPath.is_core_path?(file)) ? RbxPath.convert_path_to_full(file) : file
end
def backtrace_line
Array(ex.bt_source_location_for(backtrace_level)).last
end
def check_for_errors
raise CommandError, "No exception found." unless ex
raise CommandError, "The given backtrace level is out of bounds." unless backtrace_file
end
def start_and_end_line_for_code_window
start_line = backtrace_line - code_window_size
start_line = 1 if start_line < 1
[start_line, backtrace_line + code_window_size]
end
def header
unindent %{
#{Helpers::Text.bold 'Exception:'} #{ex.class}: #{ex.message}
--
#{Helpers::Text.bold('From:')} #{backtrace_file} @ line #{backtrace_line} @ #{Helpers::Text.bold("level: #{backtrace_level}")} of backtrace (of #{ex.backtrace.size - 1}).
}
end
end
end
end

View File

@ -0,0 +1,84 @@
class Pry
class Command::Cat
class FileFormatter < AbstractFormatter
attr_accessor :file_with_embedded_line
attr_accessor :opts
attr_accessor :_pry_
def initialize(file_with_embedded_line, _pry_, opts)
@file_with_embedded_line = file_with_embedded_line
@opts = opts
@_pry_ = _pry_
end
def file_and_line
file_name, line_num = file_with_embedded_line.split(':')
[File.expand_path(file_name), line_num ? line_num.to_i : nil]
end
def file_name
file_and_line.first
end
def line_number
file_and_line.last
end
def code_window_size
Pry.config.default_window_size || 7
end
def format
raise CommandError, "Must provide a filename, --in, or --ex." if !file_with_embedded_line
set_file_and_dir_locals(file_name, _pry_, _pry_.current_context)
decorate(Pry::Code.from_file(file_name))
end
private
def decorate(content)
line_number ? super.around(line_number, code_window_size) : super
end
def code_type
opts[:type] || detect_code_type_from_file(file_name)
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", "pryrc", "irbrc"
: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

View File

@ -0,0 +1,42 @@
class Pry
class Command::Cat
class InputExpressionFormatter < AbstractFormatter
attr_accessor :input_expressions
attr_accessor :opts
def initialize(input_expressions, opts)
@input_expressions = input_expressions
@opts = opts
end
def format
raise CommandError, "No input expressions!" if numbered_input_items.length < 1
if numbered_input_items.length > 1
content = ""
numbered_input_items.each do |i, s|
content << "#{Helpers::Text.bold(i.to_s)}:\n" << decorate(Pry::Code(s).with_indentation(2)).to_s
end
content
else
decorate(Pry::Code(selected_input_items.first)).to_s
end
end
private
def selected_input_items
input_expressions[normalized_expression_range] || []
end
def numbered_input_items
@numbered_input_items ||= normalized_expression_range.zip(selected_input_items).
reject { |_, s| s.nil? || s == "" }
end
def normalized_expression_range
absolute_index_range(opts[:i], input_expressions.length)
end
end
end
end