mirror of
https://github.com/pry/pry.git
synced 2022-11-09 12:35:05 -05:00
Moved helpers to their own namespaces, got rid of CommandBase
This commit is contained in:
parent
1dccc80f2d
commit
b0f8b72da0
9 changed files with 344 additions and 548 deletions
|
@ -6,6 +6,9 @@ require 'shellwords'
|
|||
require "readline"
|
||||
require "stringio"
|
||||
require "coderay"
|
||||
require "optparse"
|
||||
require "slop"
|
||||
require "rubygems/dependency_installer"
|
||||
|
||||
if RUBY_PLATFORM =~ /mswin/ || RUBY_PLATFORM =~ /mingw/
|
||||
begin
|
||||
|
@ -19,7 +22,7 @@ end
|
|||
require "pry/version"
|
||||
require "pry/hooks"
|
||||
require "pry/print"
|
||||
require "pry/command_base" # to be removed
|
||||
require "pry/helpers"
|
||||
require "pry/command_set"
|
||||
require "pry/commands"
|
||||
require "pry/command_context"
|
||||
|
|
|
@ -1,202 +0,0 @@
|
|||
require 'rubygems/dependency_installer'
|
||||
require "pry/command_base_helpers"
|
||||
|
||||
class Pry
|
||||
|
||||
# Basic command functionality. All user-defined commands must
|
||||
# inherit from this class. It provides the `command` method.
|
||||
class CommandBase
|
||||
class << self
|
||||
include CommandBaseHelpers
|
||||
|
||||
attr_accessor :commands
|
||||
attr_accessor :opts, :output, :target
|
||||
|
||||
# private because we want to force function style invocation. We require
|
||||
# that the location where the block is defined has the `opts`
|
||||
# method in scope.
|
||||
private
|
||||
|
||||
# Defines a new Pry command.
|
||||
# @param [String, Array] names The name of the command (or array of
|
||||
# command name aliases).
|
||||
# @param [String] description A description of the command.
|
||||
# @param [Hash] options The optional configuration parameters.
|
||||
# @option options [Boolean] :keep_retval Whether or not to use return value
|
||||
# of the block for return of `command` or just to return `nil`
|
||||
# (the default).
|
||||
# @yield The action to perform. The parameters in the block
|
||||
# determines the parameters the command will receive. All
|
||||
# parameters passed into the block will be strings. Successive
|
||||
# command parameters are separated by whitespace at the Pry prompt.
|
||||
# @example
|
||||
# class MyCommands < Pry::CommandBase
|
||||
# command "greet", "Greet somebody" do |name|
|
||||
# puts "Good afternoon #{name.capitalize}!"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # From pry:
|
||||
# # pry(main)> _pry_.commands = MyCommands
|
||||
# # pry(main)> greet john
|
||||
# # Good afternoon John!
|
||||
# # pry(main)> help greet
|
||||
# # Greet somebody
|
||||
def command(names, description="No description.", options={}, &block)
|
||||
options = {
|
||||
:keep_retval => false,
|
||||
:requires_gem => nil
|
||||
}.merge!(options)
|
||||
|
||||
@commands ||= {}
|
||||
|
||||
if command_dependencies_met?(options)
|
||||
Array(names).each do |name|
|
||||
commands[name] = {
|
||||
:description => description,
|
||||
:action => block,
|
||||
:keep_retval => options[:keep_retval]
|
||||
}
|
||||
end
|
||||
else
|
||||
create_command_stub(names, description, options, block)
|
||||
end
|
||||
end
|
||||
|
||||
# Delete a command or an array of commands.
|
||||
# Useful when inheriting from another command set and pruning
|
||||
# those commands down to the ones you want.
|
||||
# @param [Array<String>] names The command name or array
|
||||
# of command names you want to delete
|
||||
# @example Deleteing inherited commands
|
||||
# class MyCommands < Pry::Commands
|
||||
# delete "show_method", "show_imethod", "show_doc", "show_idoc"
|
||||
# end
|
||||
# Pry.commands = MyCommands
|
||||
def delete(*names)
|
||||
names.each { |name| commands.delete(name) }
|
||||
end
|
||||
|
||||
# Execute a command (this enables commands to call other commands).
|
||||
# @param [String] name The command to execute
|
||||
# @param [Array] args The parameters to pass to the command.
|
||||
# @example Wrap one command with another
|
||||
# class MyCommands < Pry::Commands
|
||||
# command "ls2" do
|
||||
# output.puts "before ls"
|
||||
# run "ls"
|
||||
# output.puts "after ls"
|
||||
# end
|
||||
# end
|
||||
def run(name, *args)
|
||||
command_processor = CommandProcessor.new(target.eval('_pry_'))
|
||||
|
||||
if command_processor.system_command?(name)
|
||||
command_processor.execute_system_command("#{name} #{args.join(' ')}", target)
|
||||
else
|
||||
raise "#{name.inspect} is not a valid pry command." unless opts[:commands].include? name
|
||||
action = opts[:commands][name][:action]
|
||||
instance_exec(*args, &action)
|
||||
end
|
||||
end
|
||||
|
||||
# Import commands from another command object.
|
||||
# @param [Pry::CommandBase] klass The class to import from (must
|
||||
# be a subclass of `Pry::CommandBase`)
|
||||
# @param [Array<String>] names The commands to import.
|
||||
# @example
|
||||
# class MyCommands < Pry::CommandBase
|
||||
# import_from Pry::Commands, "ls", "show_method", "cd"
|
||||
# end
|
||||
def import_from(klass, *names)
|
||||
imported_hash = Hash[klass.commands.select { |k, v| names.include?(k) }]
|
||||
commands.merge!(imported_hash)
|
||||
end
|
||||
|
||||
# Create an alias for a command.
|
||||
# @param [String] new_command The alias name.
|
||||
# @param [String] orig_command The original command name.
|
||||
# @param [String] desc The optional description.
|
||||
# @example
|
||||
# class MyCommands < Pry::CommandBase
|
||||
# alias_command "help_alias", "help"
|
||||
# end
|
||||
def alias_command(new_command_name, orig_command_name, desc=nil)
|
||||
commands[new_command_name] = commands[orig_command_name].dup
|
||||
commands[new_command_name][:description] = desc if desc
|
||||
end
|
||||
|
||||
# Set the description for a command (replacing the old
|
||||
# description.)
|
||||
# @param [String] name The command name.
|
||||
# @param [String] description The command description.
|
||||
# @example
|
||||
# class MyCommands < Pry::CommandBase
|
||||
# desc "help", "help description"
|
||||
# end
|
||||
def desc(name, description)
|
||||
commands[name][:description] = description
|
||||
end
|
||||
end
|
||||
|
||||
command "help", "This menu." do |cmd|
|
||||
command_info = opts[:commands]
|
||||
|
||||
if !cmd
|
||||
output.puts
|
||||
help_text = heading("Command List:") + "\n"
|
||||
command_info.each do |k, data|
|
||||
if !data[:stub_info]
|
||||
help_text << ("#{k}".ljust(18) + data[:description] + "\n") if !data[:description].empty?
|
||||
else
|
||||
help_text << (bold("#{k}".ljust(18) + data[:description] + "\n")) if !data[:description].empty?
|
||||
end
|
||||
end
|
||||
stagger_output(help_text)
|
||||
else
|
||||
if command_info[cmd]
|
||||
output.puts command_info[cmd][:description]
|
||||
else
|
||||
output.puts "No info for command: #{cmd}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
command "install", "Install a disabled command." do |name|
|
||||
stub_info = commands[name][:stub_info]
|
||||
|
||||
if !stub_info
|
||||
output.puts "Not a command stub. Nothing to do."
|
||||
next
|
||||
end
|
||||
|
||||
output.puts "Attempting to install `#{name}` command..."
|
||||
gems_to_install = Array(stub_info[:requires_gem])
|
||||
|
||||
gem_install_failed = false
|
||||
gems_to_install.each do |g|
|
||||
next if gem_installed?(g)
|
||||
output.puts "Installing `#{g}` gem..."
|
||||
|
||||
begin
|
||||
Gem::DependencyInstaller.new.install(g)
|
||||
rescue Gem::GemNotFoundException
|
||||
output.puts "Required Gem: `#{g}` not found. Aborting command installation."
|
||||
gem_install_failed = true
|
||||
next
|
||||
end
|
||||
end
|
||||
next if gem_install_failed
|
||||
|
||||
Gem.refresh
|
||||
load "#{File.dirname(__FILE__)}/commands.rb"
|
||||
output.puts "Installation of `#{name}` successful! Type `help #{name}` for information"
|
||||
end
|
||||
|
||||
# Ensures that commands can be inherited
|
||||
def self.inherited(klass)
|
||||
klass.commands = commands.dup
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -7,7 +7,7 @@ class Pry
|
|||
attr_accessor :opts
|
||||
attr_accessor :commands
|
||||
|
||||
include Pry::CommandBase::CommandBaseHelpers
|
||||
include Pry::CommandHelpers
|
||||
include Pry::Helpers::BaseHelpers
|
||||
include Pry::Helpers::CommandHelpers
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,327 +0,0 @@
|
|||
class Pry
|
||||
module CommandHelpers
|
||||
|
||||
private
|
||||
|
||||
def try_to_load_pry_doc
|
||||
|
||||
# YARD crashes on rbx, so do not require it
|
||||
if !Object.const_defined?(:RUBY_ENGINE) || RUBY_ENGINE !~ /rbx/
|
||||
require "pry-doc"
|
||||
end
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def set_file_and_dir_locals(file_name)
|
||||
return if !target
|
||||
$_file_temp = File.expand_path(file_name)
|
||||
$_dir_temp = File.dirname($_file_temp)
|
||||
target.eval("_file_ = $_file_temp")
|
||||
target.eval("_dir_ = $_dir_temp")
|
||||
end
|
||||
|
||||
def add_line_numbers(lines, start_line)
|
||||
line_array = lines.each_line.to_a
|
||||
line_array.each_with_index.map do |line, idx|
|
||||
adjusted_index = idx + start_line
|
||||
if Pry.color
|
||||
cindex = CodeRay.scan("#{adjusted_index}", :ruby).term
|
||||
"#{cindex}: #{line}"
|
||||
else
|
||||
"#{idx}: #{line}"
|
||||
end
|
||||
end.join
|
||||
end
|
||||
|
||||
# if start_line is not false then add line numbers starting with start_line
|
||||
def render_output(should_flood, start_line, doc)
|
||||
if start_line
|
||||
doc = add_line_numbers(doc, start_line)
|
||||
|
||||
if should_flood
|
||||
output.puts doc
|
||||
else
|
||||
stagger_output(doc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def editor_with_start_line(line_number)
|
||||
case Pry.editor
|
||||
when /^[gm]?vi/, /^emacs/, /^nano/, /^pico/, /^gedit/, /^kate/
|
||||
"#{Pry.editor} +#{line_number}"
|
||||
when /^mate/
|
||||
"#{Pry.editor} -l#{line_number}"
|
||||
else
|
||||
if RUBY_PLATFORM =~ /mswin|mingw/
|
||||
Pry.editor
|
||||
else
|
||||
"#{Pry.editor} +#{line_number}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def is_a_dynamically_defined_method?(meth)
|
||||
file, _ = meth.source_location
|
||||
!!(file =~ /(\(.*\))|<.*>/)
|
||||
end
|
||||
|
||||
def check_for_dynamically_defined_method(meth)
|
||||
if is_a_dynamically_defined_method?(meth)
|
||||
raise "Cannot retrieve source for dynamically defined method."
|
||||
end
|
||||
end
|
||||
|
||||
def check_for_dynamically_defined_method(meth)
|
||||
file, _ = meth.source_location
|
||||
if file =~ /(\(.*\))|<.*>/
|
||||
raise "Cannot retrieve source for dynamically defined method."
|
||||
end
|
||||
end
|
||||
|
||||
def remove_first_word(text)
|
||||
text.split.drop(1).join(' ')
|
||||
end
|
||||
|
||||
# turn off color for duration of block
|
||||
def no_color(&block)
|
||||
old_color_state = Pry.color
|
||||
Pry.color = false
|
||||
yield
|
||||
ensure
|
||||
Pry.color = old_color_state
|
||||
end
|
||||
|
||||
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
|
||||
code = strip_leading_whitespace(meth.source)
|
||||
set_file_and_dir_locals(meth.source_location.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
|
||||
doc = meth.comment
|
||||
doc = strip_leading_hash_and_whitespace_from_ruby_comments(doc)
|
||||
set_file_and_dir_locals(meth.source_location.first)
|
||||
end
|
||||
|
||||
[doc, code_type]
|
||||
end
|
||||
|
||||
def get_method_object(meth_name, target, options)
|
||||
if !meth_name
|
||||
return nil
|
||||
end
|
||||
|
||||
if options[:M]
|
||||
target.eval("instance_method(:#{meth_name})")
|
||||
elsif options[:m]
|
||||
target.eval("method(:#{meth_name})")
|
||||
else
|
||||
begin
|
||||
target.eval("instance_method(:#{meth_name})")
|
||||
rescue
|
||||
begin
|
||||
target.eval("method(:#{meth_name})")
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_header(meth, code_type, content)
|
||||
num_lines = "Number of lines: #{bold(content.each_line.count.to_s)}"
|
||||
case code_type
|
||||
when :ruby
|
||||
file, line = meth.source_location
|
||||
"\n#{bold('From:')} #{file} @ line #{line}:\n#{num_lines}\n\n"
|
||||
else
|
||||
file = Pry::MethodInfo.info_for(meth).file
|
||||
"\n#{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.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
|
||||
CodeRay.scan(contents, language_detected).term
|
||||
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
|
||||
|
||||
# documentation related helpers
|
||||
def strip_color_codes(str)
|
||||
str.gsub(/\e\[.*?(\d)+m/, '')
|
||||
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[32m#{$1}\e[0m": $1 }.
|
||||
gsub(/<i>(?:\s*\n)?(.*?)\s*<\/i>/m) { Pry.color ? "\e[34m#{$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
|
||||
output = comment.lines.map do |v|
|
||||
if in_tag_block && v !~ /^\S/
|
||||
strip_color_codes(strip_color_codes(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 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
|
|
@ -19,7 +19,7 @@ class Pry
|
|||
end
|
||||
end
|
||||
|
||||
include Pry::CommandBase::CommandBaseHelpers
|
||||
include Pry::Helpers::BaseHelpers
|
||||
|
||||
attr_reader :commands
|
||||
attr_reader :name
|
||||
|
|
|
@ -1,17 +1,8 @@
|
|||
require "optparse"
|
||||
require "method_source"
|
||||
require 'slop'
|
||||
require 'rubygems/dependency_installer'
|
||||
require "pry/command_base"
|
||||
require "pry/pry_instance"
|
||||
require "pry/command_helpers"
|
||||
|
||||
class Pry
|
||||
|
||||
# Default commands used by Pry.
|
||||
Commands = Pry::CommandSet.new :default do
|
||||
extend CommandHelpers
|
||||
try_to_load_pry_doc
|
||||
Helpers::CommandHelpers.try_to_load_pry_doc
|
||||
|
||||
command "!", "Clear the input buffer. Useful if the parsing process goes wrong and you get stuck in the read loop." do
|
||||
output.puts "Input buffer cleared!"
|
||||
|
|
2
lib/pry/helpers.rb
Normal file
2
lib/pry/helpers.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
require "pry/helpers/base_helpers"
|
||||
require "pry/helpers/command_helpers"
|
|
@ -1,8 +1,8 @@
|
|||
class Pry
|
||||
class CommandBase
|
||||
module CommandBaseHelpers
|
||||
module Helpers
|
||||
|
||||
private
|
||||
module BaseHelpers
|
||||
module_function
|
||||
|
||||
def gem_installed?(gem_name)
|
||||
require 'rubygems'
|
||||
|
@ -237,5 +237,3 @@ class Pry
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
331
lib/pry/helpers/command_helpers.rb
Normal file
331
lib/pry/helpers/command_helpers.rb
Normal file
|
@ -0,0 +1,331 @@
|
|||
class Pry
|
||||
module Helpers
|
||||
|
||||
module CommandHelpers
|
||||
|
||||
module_function
|
||||
|
||||
def try_to_load_pry_doc
|
||||
|
||||
# YARD crashes on rbx, so do not require it
|
||||
if !Object.const_defined?(:RUBY_ENGINE) || RUBY_ENGINE !~ /rbx/
|
||||
require "pry-doc"
|
||||
end
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
def set_file_and_dir_locals(file_name)
|
||||
return if !target
|
||||
$_file_temp = File.expand_path(file_name)
|
||||
$_dir_temp = File.dirname($_file_temp)
|
||||
target.eval("_file_ = $_file_temp")
|
||||
target.eval("_dir_ = $_dir_temp")
|
||||
end
|
||||
|
||||
def add_line_numbers(lines, start_line)
|
||||
line_array = lines.each_line.to_a
|
||||
line_array.each_with_index.map do |line, idx|
|
||||
adjusted_index = idx + start_line
|
||||
if Pry.color
|
||||
cindex = CodeRay.scan("#{adjusted_index}", :ruby).term
|
||||
"#{cindex}: #{line}"
|
||||
else
|
||||
"#{idx}: #{line}"
|
||||
end
|
||||
end.join
|
||||
end
|
||||
|
||||
# if start_line is not false then add line numbers starting with start_line
|
||||
def render_output(should_flood, start_line, doc)
|
||||
if start_line
|
||||
doc = add_line_numbers(doc, start_line)
|
||||
|
||||
if should_flood
|
||||
output.puts doc
|
||||
else
|
||||
stagger_output(doc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def editor_with_start_line(line_number)
|
||||
case Pry.editor
|
||||
when /^[gm]?vi/, /^emacs/, /^nano/, /^pico/, /^gedit/, /^kate/
|
||||
"#{Pry.editor} +#{line_number}"
|
||||
when /^mate/
|
||||
"#{Pry.editor} -l#{line_number}"
|
||||
else
|
||||
if RUBY_PLATFORM =~ /mswin|mingw/
|
||||
Pry.editor
|
||||
else
|
||||
"#{Pry.editor} +#{line_number}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def is_a_dynamically_defined_method?(meth)
|
||||
file, _ = meth.source_location
|
||||
!!(file =~ /(\(.*\))|<.*>/)
|
||||
end
|
||||
|
||||
def check_for_dynamically_defined_method(meth)
|
||||
if is_a_dynamically_defined_method?(meth)
|
||||
raise "Cannot retrieve source for dynamically defined method."
|
||||
end
|
||||
end
|
||||
|
||||
def check_for_dynamically_defined_method(meth)
|
||||
file, _ = meth.source_location
|
||||
if file =~ /(\(.*\))|<.*>/
|
||||
raise "Cannot retrieve source for dynamically defined method."
|
||||
end
|
||||
end
|
||||
|
||||
def remove_first_word(text)
|
||||
text.split.drop(1).join(' ')
|
||||
end
|
||||
|
||||
# turn off color for duration of block
|
||||
def no_color(&block)
|
||||
old_color_state = Pry.color
|
||||
Pry.color = false
|
||||
yield
|
||||
ensure
|
||||
Pry.color = old_color_state
|
||||
end
|
||||
|
||||
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
|
||||
code = strip_leading_whitespace(meth.source)
|
||||
set_file_and_dir_locals(meth.source_location.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
|
||||
doc = meth.comment
|
||||
doc = strip_leading_hash_and_whitespace_from_ruby_comments(doc)
|
||||
set_file_and_dir_locals(meth.source_location.first)
|
||||
end
|
||||
|
||||
[doc, code_type]
|
||||
end
|
||||
|
||||
def get_method_object(meth_name, target, options)
|
||||
if !meth_name
|
||||
return nil
|
||||
end
|
||||
|
||||
if options[:M]
|
||||
target.eval("instance_method(:#{meth_name})")
|
||||
elsif options[:m]
|
||||
target.eval("method(:#{meth_name})")
|
||||
else
|
||||
begin
|
||||
target.eval("instance_method(:#{meth_name})")
|
||||
rescue
|
||||
begin
|
||||
target.eval("method(:#{meth_name})")
|
||||
rescue
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_header(meth, code_type, content)
|
||||
num_lines = "Number of lines: #{bold(content.each_line.count.to_s)}"
|
||||
case code_type
|
||||
when :ruby
|
||||
file, line = meth.source_location
|
||||
"\n#{bold('From:')} #{file} @ line #{line}:\n#{num_lines}\n\n"
|
||||
else
|
||||
file = Pry::MethodInfo.info_for(meth).file
|
||||
"\n#{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.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
|
||||
CodeRay.scan(contents, language_detected).term
|
||||
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
|
||||
|
||||
# documentation related helpers
|
||||
def strip_color_codes(str)
|
||||
str.gsub(/\e\[.*?(\d)+m/, '')
|
||||
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[32m#{$1}\e[0m": $1 }.
|
||||
gsub(/<i>(?:\s*\n)?(.*?)\s*<\/i>/m) { Pry.color ? "\e[34m#{$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
|
||||
output = comment.lines.map do |v|
|
||||
if in_tag_block && v !~ /^\S/
|
||||
strip_color_codes(strip_color_codes(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 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
|
Loading…
Reference in a new issue