diff --git a/lib/pry.rb b/lib/pry.rb index fbe2809f..b67781e2 100644 --- a/lib/pry.rb +++ b/lib/pry.rb @@ -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" diff --git a/lib/pry/command_base.rb b/lib/pry/command_base.rb deleted file mode 100644 index 1e3b8e67..00000000 --- a/lib/pry/command_base.rb +++ /dev/null @@ -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] 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] 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 diff --git a/lib/pry/command_context.rb b/lib/pry/command_context.rb index f2d14b96..ea22165b 100644 --- a/lib/pry/command_context.rb +++ b/lib/pry/command_context.rb @@ -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 diff --git a/lib/pry/command_helpers.rb b/lib/pry/command_helpers.rb deleted file mode 100644 index dc335340..00000000 --- a/lib/pry/command_helpers.rb +++ /dev/null @@ -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(/(?:\s*\n)?(.*?)\s*<\/code>/m) { Pry.color ? CodeRay.scan($1, code_type).term : $1 }. - gsub(/(?:\s*\n)?(.*?)\s*<\/em>/m) { Pry.color ? "\e[32m#{$1}\e[0m": $1 }. - gsub(/(?:\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 diff --git a/lib/pry/command_set.rb b/lib/pry/command_set.rb index 71076be2..6ae92227 100644 --- a/lib/pry/command_set.rb +++ b/lib/pry/command_set.rb @@ -19,7 +19,7 @@ class Pry end end - include Pry::CommandBase::CommandBaseHelpers + include Pry::Helpers::BaseHelpers attr_reader :commands attr_reader :name diff --git a/lib/pry/commands.rb b/lib/pry/commands.rb index cd1b50db..59e102e1 100644 --- a/lib/pry/commands.rb +++ b/lib/pry/commands.rb @@ -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!" diff --git a/lib/pry/helpers.rb b/lib/pry/helpers.rb new file mode 100644 index 00000000..30681922 --- /dev/null +++ b/lib/pry/helpers.rb @@ -0,0 +1,2 @@ +require "pry/helpers/base_helpers" +require "pry/helpers/command_helpers" diff --git a/lib/pry/command_base_helpers.rb b/lib/pry/helpers/base_helpers.rb similarity index 99% rename from lib/pry/command_base_helpers.rb rename to lib/pry/helpers/base_helpers.rb index f28e3854..8d5f5dfe 100644 --- a/lib/pry/command_base_helpers.rb +++ b/lib/pry/helpers/base_helpers.rb @@ -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 - - diff --git a/lib/pry/helpers/command_helpers.rb b/lib/pry/helpers/command_helpers.rb new file mode 100644 index 00000000..aa7e2207 --- /dev/null +++ b/lib/pry/helpers/command_helpers.rb @@ -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(/(?:\s*\n)?(.*?)\s*<\/code>/m) { Pry.color ? CodeRay.scan($1, code_type).term : $1 }. + gsub(/(?:\s*\n)?(.*?)\s*<\/em>/m) { Pry.color ? "\e[32m#{$1}\e[0m": $1 }. + gsub(/(?:\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