mirror of
https://github.com/pry/pry.git
synced 2022-11-09 12:35:05 -05:00
416 lines
13 KiB
Ruby
416 lines
13 KiB
Ruby
class Pry
|
|
module Helpers
|
|
|
|
module CommandHelpers
|
|
|
|
module_function
|
|
|
|
def meth_name_from_binding(b)
|
|
meth_name = b.eval('__method__')
|
|
if [:__script__, nil, :__binding__, :__binding_impl__].include?(meth_name)
|
|
nil
|
|
else
|
|
meth_name
|
|
end
|
|
end
|
|
|
|
# if start_line is not false then add line numbers starting with start_line
|
|
def render_output(should_flood, start_line, text, color=:blue)
|
|
if start_line
|
|
text = Pry::Helpers::Text.with_line_numbers text, start_line, color
|
|
end
|
|
|
|
if should_flood
|
|
output.puts text
|
|
else
|
|
stagger_output(text)
|
|
end
|
|
end
|
|
|
|
def is_a_dynamically_defined_method?(meth)
|
|
file, _ = meth.source_location
|
|
!!(file =~ /(\(.*\))|<.*>/)
|
|
end
|
|
|
|
def check_for_dynamically_defined_method(meth)
|
|
file, _ = meth.source_location
|
|
if file =~ /(\(.*\))|<.*>/ && file != Pry.eval_path
|
|
raise "Cannot retrieve source for dynamically defined method."
|
|
end
|
|
end
|
|
|
|
########### RBX HELPERS #############
|
|
def is_core_rbx_path?(path)
|
|
rbx? &&
|
|
path.start_with?("kernel")
|
|
end
|
|
|
|
def rbx_core?(meth)
|
|
meth.source_location &&
|
|
is_core_rbx_path?(meth.source_location.first)
|
|
end
|
|
|
|
def rvm_ruby?(path)
|
|
!!(path =~ /\.rvm/)
|
|
end
|
|
|
|
def rbx_core_code_for(meth)
|
|
rbx_core_code_or_doc_for(meth, :code)
|
|
end
|
|
|
|
def rbx_core_doc_for(meth)
|
|
rbx_core_code_or_doc_for(meth, :doc)
|
|
end
|
|
|
|
def rbx_core_code_or_doc_for(meth, code_or_doc)
|
|
path_line = rbx_core_path_line_for(meth)
|
|
|
|
case code_or_doc
|
|
when :code
|
|
MethodSource.source_helper(path_line)
|
|
when :doc
|
|
MethodSource.comment_helper(path_line)
|
|
end
|
|
end
|
|
|
|
def rbx_convert_path_to_full(path)
|
|
if rvm_ruby?(Rubinius::BIN_PATH)
|
|
rbx_rvm_convert_path_to_full(path)
|
|
else
|
|
rbx_std_convert_path_to_full(path)
|
|
end
|
|
end
|
|
|
|
def rbx_rvm_convert_path_to_full(path)
|
|
ruby_name = File.dirname(Rubinius::BIN_PATH).split("/").last
|
|
source_path = File.join(File.dirname(File.dirname(File.dirname(Rubinius::BIN_PATH))), "src", ruby_name)
|
|
file_name = File.join(source_path, path)
|
|
raise "Cannot find rbx core source" if !File.exists?(file_name)
|
|
file_name
|
|
end
|
|
|
|
def rbx_std_convert_path_to_full(path)
|
|
file_name = File.join(Rubinius::BIN_PATH, "..", path)
|
|
raise "Cannot find rbx core source" if !File.exists?(file_name)
|
|
file_name
|
|
end
|
|
|
|
def rbx_core_path_line_for(meth)
|
|
if rvm_ruby?(Rubinius::BIN_PATH)
|
|
rvm_rbx_core_path_line_for(meth)
|
|
else
|
|
std_rbx_core_path_line_for(meth)
|
|
end
|
|
end
|
|
|
|
def std_rbx_core_path_line_for(meth)
|
|
file_name = rbx_std_convert_path_to_full(meth.source_location.first)
|
|
start_line = meth.source_location.last
|
|
|
|
[file_name, start_line]
|
|
end
|
|
|
|
def rvm_rbx_core_path_line_for(meth)
|
|
file_name = rbx_rvm_convert_path_to_full(meth.source_location.first)
|
|
start_line = meth.source_location.last
|
|
|
|
[file_name, start_line]
|
|
end
|
|
|
|
######### END RBX HELPERS ###############
|
|
|
|
def code_and_code_type_for(meth)
|
|
case code_type = code_type_for(meth)
|
|
when nil
|
|
return nil
|
|
when :c
|
|
code = Pry::MethodInfo.info_for(meth).source
|
|
code = strip_comments_from_c_code(code)
|
|
when :ruby
|
|
if meth.source_location.first == Pry.eval_path
|
|
start_line = meth.source_location.last
|
|
p = Pry.new(:input => StringIO.new(Pry.line_buffer[start_line..-1].join)).r(target)
|
|
code = strip_leading_whitespace(p)
|
|
else
|
|
if rbx_core?(meth)
|
|
code = strip_leading_whitespace(rbx_core_code_for(meth))
|
|
else
|
|
code = strip_leading_whitespace(meth.source)
|
|
end
|
|
end
|
|
set_file_and_dir_locals(path_line_for(meth).first)
|
|
end
|
|
|
|
[code, code_type]
|
|
end
|
|
|
|
def doc_and_code_type_for(meth)
|
|
case code_type = code_type_for(meth)
|
|
when nil
|
|
return nil
|
|
when :c
|
|
doc = Pry::MethodInfo.info_for(meth).docstring
|
|
when :ruby
|
|
if rbx_core?(meth)
|
|
doc = strip_leading_hash_and_whitespace_from_ruby_comments(rbx_core_doc_for(meth))
|
|
else
|
|
doc = strip_leading_hash_and_whitespace_from_ruby_comments(meth.comment)
|
|
end
|
|
set_file_and_dir_locals(path_line_for(meth).first)
|
|
end
|
|
|
|
[doc, code_type]
|
|
end
|
|
|
|
def get_method_object(meth_name, target, options)
|
|
if meth_name
|
|
if meth_name =~ /(\S+)\#(\S+)\Z/
|
|
context, meth_name = $1, $2
|
|
target = Pry.binding_for(target.eval(context))
|
|
options["instance-methods"] = true
|
|
options[:methods] = false
|
|
elsif meth_name =~ /(\S+)\.(\S+)\Z/
|
|
context, meth_name = $1, $2
|
|
target = Pry.binding_for(target.eval(context))
|
|
options["instance-methods"] = false
|
|
options[:methods] = true
|
|
end
|
|
else
|
|
meth_name = meth_name_from_binding(target)
|
|
end
|
|
|
|
if !meth_name
|
|
return nil
|
|
end
|
|
|
|
if options["instance-methods"]
|
|
target.eval("instance_method(:#{meth_name})") rescue nil
|
|
elsif options[:methods]
|
|
target.eval("method(:#{meth_name})") rescue nil
|
|
else
|
|
begin
|
|
target.eval("instance_method(:#{meth_name})")
|
|
rescue
|
|
target.eval("method(:#{meth_name})") rescue nil
|
|
end
|
|
end
|
|
end
|
|
|
|
def path_line_for(meth)
|
|
if rbx_core?(meth)
|
|
rbx_core_path_line_for(meth)
|
|
else
|
|
meth.source_location
|
|
end
|
|
end
|
|
|
|
def make_header(meth, code_type, content)
|
|
num_lines = "Number of lines: #{Pry::Helpers::Text.bold(content.each_line.count.to_s)}"
|
|
case code_type
|
|
when :ruby
|
|
file, line = path_line_for(meth)
|
|
"\n#{Pry::Helpers::Text.bold('From:')} #{file} @ line #{line}:\n#{num_lines}\n\n"
|
|
else
|
|
file = Pry::MethodInfo.info_for(meth).file
|
|
"\n#{Pry::Helpers::Text.bold('From:')} #{file} in Ruby Core (C Method):\n#{num_lines}\n\n"
|
|
end
|
|
end
|
|
|
|
def is_a_c_method?(meth)
|
|
meth.source_location.nil?
|
|
end
|
|
|
|
def should_use_pry_doc?(meth)
|
|
Pry.config.has_pry_doc && is_a_c_method?(meth)
|
|
end
|
|
|
|
def code_type_for(meth)
|
|
# only C methods
|
|
if should_use_pry_doc?(meth)
|
|
info = Pry::MethodInfo.info_for(meth)
|
|
if info && info.source
|
|
code_type = :c
|
|
else
|
|
output.puts "Cannot find C method: #{meth.name}"
|
|
code_type = nil
|
|
end
|
|
else
|
|
if is_a_c_method?(meth)
|
|
output.puts "Cannot locate this method: #{meth.name}. Try `gem install pry-doc` to get access to Ruby Core documentation."
|
|
code_type = nil
|
|
else
|
|
check_for_dynamically_defined_method(meth)
|
|
code_type = :ruby
|
|
end
|
|
end
|
|
code_type
|
|
end
|
|
|
|
def file_map
|
|
{
|
|
[".c", ".h"] => :c,
|
|
[".cpp", ".hpp", ".cc", ".h", "cxx"] => :cpp,
|
|
[".rb", "Rakefile", ".irbrc", ".gemspec", ".pryrc"] => :ruby,
|
|
".py" => :python,
|
|
".diff" => :diff,
|
|
".css" => :css,
|
|
".html" => :html,
|
|
[".yaml", ".yml"] => :yaml,
|
|
".xml" => :xml,
|
|
".php" => :php,
|
|
".js" => :javascript,
|
|
".java" => :java,
|
|
".rhtml" => :rhtml,
|
|
".json" => :json
|
|
}
|
|
end
|
|
|
|
def syntax_highlight_by_file_type_or_specified(contents, file_name, file_type)
|
|
_, language_detected = file_map.find do |k, v|
|
|
Array(k).any? do |matcher|
|
|
matcher == File.extname(file_name) || matcher == File.basename(file_name)
|
|
end
|
|
end
|
|
|
|
language_detected = file_type if file_type
|
|
if Pry.color
|
|
CodeRay.scan(contents, language_detected).term
|
|
else
|
|
contents
|
|
end
|
|
end
|
|
|
|
# convert negative line numbers to positive by wrapping around
|
|
# last line (as per array indexing with negative numbers)
|
|
def normalized_line_number(line_number, total_lines)
|
|
line_number < 0 ? line_number + total_lines : line_number
|
|
end
|
|
|
|
# returns the file content between the lines and the normalized
|
|
# start and end line numbers.
|
|
def read_between_the_lines(file_name, start_line, end_line)
|
|
content = File.read(File.expand_path(file_name))
|
|
lines_array = content.each_line.to_a
|
|
|
|
[lines_array[start_line..end_line].join, normalized_line_number(start_line, lines_array.size),
|
|
normalized_line_number(end_line, lines_array.size)]
|
|
end
|
|
|
|
def process_rdoc(comment, code_type)
|
|
comment = comment.dup
|
|
comment.gsub(/<code>(?:\s*\n)?(.*?)\s*<\/code>/m) { Pry.color ? CodeRay.scan($1, code_type).term : $1 }.
|
|
gsub(/<em>(?:\s*\n)?(.*?)\s*<\/em>/m) { Pry.color ? "\e[1m#{$1}\e[0m": $1 }.
|
|
gsub(/<i>(?:\s*\n)?(.*?)\s*<\/i>/m) { Pry.color ? "\e[1m#{$1}\e[0m" : $1 }.
|
|
gsub(/\B\+(\w*?)\+\B/) { Pry.color ? "\e[32m#{$1}\e[0m": $1 }.
|
|
gsub(/((?:^[ \t]+.+(?:\n+|\Z))+)/) { Pry.color ? CodeRay.scan($1, code_type).term : $1 }.
|
|
gsub(/`(?:\s*\n)?(.*?)\s*`/) { Pry.color ? CodeRay.scan($1, code_type).term : $1 }
|
|
end
|
|
|
|
def process_yardoc_tag(comment, tag)
|
|
in_tag_block = nil
|
|
comment.lines.map do |v|
|
|
if in_tag_block && v !~ /^\S/
|
|
Pry::Helpers::Text.strip_color Pry::Helpers::Text.strip_color(v)
|
|
elsif in_tag_block
|
|
in_tag_block = false
|
|
v
|
|
else
|
|
in_tag_block = true if v =~ /^@#{tag}/
|
|
v
|
|
end
|
|
end.join
|
|
end
|
|
|
|
def process_yardoc(comment)
|
|
yard_tags = ["param", "return", "option", "yield", "attr", "attr_reader", "attr_writer",
|
|
"deprecate", "example"]
|
|
(yard_tags - ["example"]).inject(comment) { |a, v| process_yardoc_tag(a, v) }.
|
|
gsub(/^@(#{yard_tags.join("|")})/) { Pry.color ? "\e[33m#{$1}\e[0m": $1 }
|
|
end
|
|
|
|
def process_comment_markup(comment, code_type)
|
|
process_yardoc process_rdoc(comment, code_type)
|
|
end
|
|
|
|
# strip leading whitespace but preserve indentation
|
|
def strip_leading_whitespace(text)
|
|
return text if text.empty?
|
|
leading_spaces = text.lines.first[/^(\s+)/, 1]
|
|
text.gsub(/^#{leading_spaces}/, '')
|
|
end
|
|
|
|
def strip_leading_hash_and_whitespace_from_ruby_comments(comment)
|
|
comment = comment.dup
|
|
comment.gsub!(/\A\#+?$/, '')
|
|
comment.gsub!(/^\s*#/, '')
|
|
strip_leading_whitespace(comment)
|
|
end
|
|
|
|
def strip_comments_from_c_code(code)
|
|
code.sub(/\A\s*\/\*.*?\*\/\s*/m, '')
|
|
end
|
|
|
|
def invoke_editor(file, line)
|
|
if Pry.config.editor.respond_to?(:call)
|
|
editor_invocation = Pry.config.editor.call(file, line)
|
|
else
|
|
editor_invocation = "#{Pry.config.editor} #{start_line_syntax_for_editor(file, line)}"
|
|
end
|
|
|
|
if jruby?
|
|
require 'spoon'
|
|
pid = Spoon.spawnp(*editor_invocation.split)
|
|
Process.waitpid(pid)
|
|
else
|
|
run ".#{editor_invocation}"
|
|
end
|
|
end
|
|
|
|
def start_line_syntax_for_editor(file_name, line_number)
|
|
file_name = file_name.gsub(/\//, '\\') if RUBY_PLATFORM =~ /mswin|mingw/
|
|
|
|
case Pry.config.editor
|
|
when /^[gm]?vi/, /^emacs/, /^nano/, /^pico/, /^gedit/, /^kate/
|
|
"+#{line_number} #{file_name}"
|
|
when /^mate/, /^geany/
|
|
"-l #{line_number} #{file_name}"
|
|
when /^uedit32/
|
|
"#{file_name}/#{line_number}"
|
|
when /^jedit/
|
|
"#{file_name} +line:#{line_number}"
|
|
else
|
|
if RUBY_PLATFORM =~ /mswin|mingw/
|
|
"#{file_name}"
|
|
else
|
|
"+#{line_number} #{file_name}"
|
|
end
|
|
end
|
|
end
|
|
|
|
def prompt(message, options="Yn")
|
|
opts = options.scan(/./)
|
|
optstring = opts.join("/") # case maintained
|
|
defaults = opts.select{|o| o.upcase == o }
|
|
opts = opts.map{|o| o.downcase}
|
|
|
|
raise "Error: Too many default values for the prompt: #{default.inspect}" if defaults.size > 1
|
|
|
|
default = defaults.first
|
|
|
|
loop do
|
|
response = Pry.input.readline("#{message} (#{optstring}) ").downcase
|
|
case response
|
|
when *opts
|
|
return response
|
|
when ""
|
|
return default.downcase
|
|
else
|
|
output.puts " |_ Invalid option: #{response.inspect}. Try again."
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
end
|