Skip to content
Browse files

added -v switch show-source and show-doc

* applies to modules/classes, displays the full list of candidate module definitions or documentation (in case of show-doc)
* TODO: a fucktonne of tests
  • Loading branch information...
1 parent d8d8041 commit b5d044eeb202891c6934c49e397b7f0963522f00 @banister banister committed Apr 17, 2012
Showing with 196 additions and 73 deletions.
  1. +1 −1 lib/pry/code.rb
  2. +72 −19 lib/pry/default_commands/introspection.rb
  3. +15 −15 lib/pry/method.rb
  4. +108 −38 lib/pry/wrapped_module.rb
View
2 lib/pry/code.rb
@@ -136,7 +136,7 @@ def from_method(meth, start_line=nil)
def from_module(mod, start_line=nil)
mod = Pry::WrappedModule(mod)
- _, start_line = mod.source_location || 1
+ start_line ||= mod.source_line || 1
new(mod.source, start_line, :ruby)
end
View
91 lib/pry/default_commands/introspection.rb
@@ -29,6 +29,13 @@ def process(name)
render_output(code_or_doc, opts)
end
+ def module_start_line(mod, candidate=0)
+ if opts.present?(:'base-one')
+ 1
+ else
+ mod.source_line_for_candidate(candidate)
+ end
+ end
end
Introspection = Pry::CommandSet.new do
@@ -48,13 +55,20 @@ def options(opt)
opt.on :l, "line-numbers", "Show line numbers."
opt.on :b, "base-one", "Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)."
opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
+ opt.on :v, :verbose, "Show docs for all candidates (modules only)"
end
def process_module(name)
- klass = target.eval(name)
+ mod = Pry::WrappedModule.from_str(name)
- mod = Pry::WrappedModule(klass)
+ if opts.present?(:verbose)
+ verbose_module(mod)
+ else
+ normal_module(mod)
+ end
+ end
+ def normal_module(mod)
# source_file reveals the underlying .c file in case of core
# classes on MRI.
# This is different to source_location, which
@@ -65,20 +79,34 @@ def process_module(name)
file_name, line = mod.source_location
end
- doc = mod.doc
-
- if doc.empty?
+ if mod.doc.empty?
output.puts "No documentation found."
+ ""
else
-
- # source_location is nil in case of core classes on MRI
set_file_and_dir_locals(file_name) if !mod.yard_docs?
- output.puts "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n\n"
+ doc = ""
+ doc << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line ? line : "N/A"}:\n\n"
+ doc << mod.doc
if opts.present?(:b) || opts.present?(:l)
start_line = mod.source_location.nil? ? 1 : line - doc.lines.count
doc = Code.new(doc, start_line, :text).
- with_line_numbers(true)
+ with_line_numbers(true).to_s
+ end
+ doc
+ end
+ end
+
+ def verbose_module(mod)
+ doc = ""
+ doc << "Found #{mod.number_of_candidates} candidates for `#{mod.name}` definition:\n"
+ mod.number_of_candidates.times do |v|
+ begin
+ doc << "\nCandidate #{v+1}/#{mod.number_of_candidates}: #{mod.source_file_for_candidate(v)} @ #{mod.source_line_for_candidate(v)}:\n\n"
+ dc = mod.doc_for_candidate(v)
+ doc << (dc.empty? ? "No documentation found.\n" : dc)
+ rescue Pry::RescuableException
+ next
end
end
doc
@@ -169,28 +197,53 @@ def options(opt)
opt.on :l, "line-numbers", "Show line numbers."
opt.on :b, "base-one", "Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)."
opt.on :f, :flood, "Do not use a pager to view text longer than one screen."
+ opt.on :v, :verbose, "Show source for all candidates (modules only)"
end
def process_method
raise CommandError, "Could not find method source" unless method_object.source
- output.puts make_header(method_object)
- output.puts "#{text.bold("Owner:")} #{method_object.owner || "N/A"}"
- output.puts "#{text.bold("Visibility:")} #{method_object.visibility}"
- output.puts
+ code = ""
+ code << make_header(method_object)
+ code << "#{text.bold("Owner:")} #{method_object.owner || "N/A"}\n"
+ code << "#{text.bold("Visibility:")} #{method_object.visibility}\n"
+ code << "\n"
- Code.from_method(method_object, start_line).
- with_line_numbers(use_line_numbers?)
+ code << Code.from_method(method_object, start_line).
+ with_line_numbers(use_line_numbers?).to_s
end
def process_module(name)
- klass = target.eval(name)
+ mod = Pry::WrappedModule.from_str(name)
+
+ if opts.present?(:verbose)
+ verbose_module(mod)
+ else
+ normal_module(mod)
+ end
- mod = Pry::WrappedModule(klass)
+ end
+
+ def normal_module(mod)
file_name, line = mod.source_location
set_file_and_dir_locals(file_name)
- output.puts "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n\n"
- Code.from_module(mod).with_line_numbers(use_line_numbers?)
+ code = ""
+ code << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n\n"
+ code << Code.from_module(mod, module_start_line(mod)).with_line_numbers(use_line_numbers?)
+ end
+
+ def verbose_module(mod)
+ code = ""
+ code << "Found #{mod.number_of_candidates} candidates for `#{mod.name}` definition:\n"
+ mod.number_of_candidates.times do |v|
+ begin
+ code << "\nCandidate #{v+1}/#{mod.number_of_candidates}: #{mod.source_file_for_candidate(v)} @ #{mod.source_line_for_candidate(v)}:\n\n"
+ code << Code.new(mod.source_for_candidate(v), module_start_line(mod, v)).with_line_numbers(use_line_numbers?).to_s
+ rescue Pry::RescuableException
+ next
+ end
+ end
+ code
end
def use_line_numbers?
View
30 lib/pry/method.rb
@@ -245,21 +245,21 @@ def name_with_owner
# @return [String, nil] The source code of the method, or `nil` if it's unavailable.
def source
@source ||= case source_type
- when :c
- info = pry_doc_info
- if info and info.source
- code = strip_comments_from_c_code(info.source)
- end
- when :ruby
- if Helpers::BaseHelpers.rbx? && !pry_method?
- code = core_code
- elsif pry_method?
- code = Pry::Code.retrieve_complete_expression_from(Pry.line_buffer[source_line..-1])
- else
- code = @method.source
- end
- strip_leading_whitespace(code)
- end
+ when :c
+ info = pry_doc_info
+ if info and info.source
+ code = strip_comments_from_c_code(info.source)
+ end
+ when :ruby
+ if Helpers::BaseHelpers.rbx? && !pry_method?
+ code = core_code
+ elsif pry_method?
+ code = Pry::Code.retrieve_complete_expression_from(Pry.line_buffer[source_line..-1])
+ else
+ code = @method.source
+ end
+ strip_leading_whitespace(code)
+ end
end
# @return [String, nil] The documentation for the method, or `nil` if it's
View
146 lib/pry/wrapped_module.rb
@@ -105,6 +105,11 @@ def yard_docs?
!!(defined?(YARD) && YARD::Registry.at(name))
end
+ def process_doc(doc)
+ process_comment_markup(strip_leading_hash_and_whitespace_from_ruby_comments(doc),
+ :ruby)
+ end
+
def doc
return @doc if @doc
@@ -116,36 +121,52 @@ def doc
elsif source_location.nil?
raise CommandError, "Can't find module's source location"
else
- @doc = extract_doc
+ @doc = extract_doc_for_candidate(0)
end
raise CommandError, "Can't find docs for module: #{name}." if !@doc
- @doc = process_comment_markup(strip_leading_hash_and_whitespace_from_ruby_comments(@doc), :ruby)
+ @doc = process_doc(@doc)
+ end
+
+ def doc_for_candidate(idx)
+ doc = extract_doc_for_candidate(idx)
+ raise CommandError, "Can't find docs for module: #{name}." if !doc
+
+ process_doc(doc)
end
# Retrieve the source for the module.
def source
- return @source if @source
+ @source ||= source_for_candidate(0)
+ end
- file, line = source_location
+ def source_for_candidate(idx)
+ file, line = module_source_location_for_candidate(idx)
raise CommandError, "Could not locate source for #{wrapped}!" if file.nil?
- @source = strip_leading_whitespace(Pry::Code.retrieve_complete_expression_from(@host_file_lines[(line - 1)..-1]))
-
+ strip_leading_whitespace(Pry::Code.retrieve_complete_expression_from(lines_for_file(file)[(line - 1)..-1]))
end
def source_file
if yard_docs?
from_yard = YARD::Registry.at(name)
from_yard.file
else
- Array(source_location).first
+ source_file_for_candidate(0)
end
end
def source_line
- source_location.nil? ? nil : source_location.last
+ source_line_for_candidate(0)
+ end
+
+ def source_file_for_candidate(idx)
+ Array(module_source_location_for_candidate(idx)).first
+ end
+
+ def source_line_for_candidate(idx)
+ Array(module_source_location_for_candidate(idx)).last
end
# Retrieve the source location of a module. Return value is in same
@@ -156,36 +177,51 @@ def source_line
# @return [Array<String, Fixnum>] The source location of the
# module (or class).
def source_location
- return @source_location if @source_location
+ @source_location ||= module_source_location_for_candidate(0)
+ rescue Pry::RescuableException
+ nil
+ end
+
+ # memoized lines for file
+ def lines_for_file(file)
+ @lines_for_file ||= {}
+
+ if file == Pry.eval_path
+ @lines_for_file[file] ||= Pry.line_buffer.drop(1)
+ else
+ @lines_for_file[file] ||= File.readlines(file)
+ end
+ end
+ def module_source_location_for_candidate(idx)
mod_type_string = wrapped.class.to_s.downcase
- file, line = find_module_method_source_location
+ file, line = method_source_location_for_candidate(idx)
return nil if !file.is_a?(String)
class_regex = /#{mod_type_string}\s*(\w*)(::)?#{wrapped.name.split(/::/).last}/
- if file == Pry.eval_path
- @host_file_lines ||= Pry.line_buffer.drop(1)
- else
- @host_file_lines ||= File.readlines(file)
- end
+ host_file_lines = lines_for_file(file)
- search_lines = @host_file_lines[0..(line - 2)]
+ search_lines = host_file_lines[0..(line - 2)]
idx = search_lines.rindex { |v| class_regex =~ v }
- @source_location = [file, idx + 1]
+ source_location = [file, idx + 1]
rescue Pry::RescuableException
nil
end
- private
+ #private
def extract_doc
- file_name, line = source_location
+ extract_doc_for_candidate(0)
+ end
+
+ def extract_doc_for_candidate(idx)
+ file_name, line = module_source_location_for_candidate(idx)
buffer = ""
- @host_file_lines[0..(line - 2)].each do |line|
+ lines_for_file(source_file_for_candidate(idx))[0..(line - 2)].each do |line|
# Add any line that is a valid ruby comment,
# but clear as soon as we hit a non comment line.
if (line =~ /^\s*#/) || (line =~ /^\s*$/)
@@ -198,32 +234,66 @@ def extract_doc
buffer
end
- def find_module_method_source_location
- ims = Pry::Method.all_from_class(wrapped, false) + Pry::Method.all_from_obj(wrapped, false)
+ # FIXME: this method is also found in Pry::Method
+ def safe_send(obj, method, *args, &block)
+ (Module === obj ? Module : Object).instance_method(method).bind(obj).call(*args, &block)
+ end
- file, line = ims.each do |m|
- break m.source_location if m.source_location && !m.alias?
- end
+ # FIXME: a variant of this method is also found in Pry::Method
+ def all_from_common(mod, method_type)
+ %w(public protected private).map do |visibility|
+ safe_send(mod, :"#{visibility}_#{method_type}s", false).select do |method_name|
+ if method_type == :method
+ safe_send(mod, method_type, method_name).owner == class << mod; self; end
+ else
+ safe_send(mod, method_type, method_name).owner == mod
+ end
+ end.map do |method_name|
+ Pry::Method.new(safe_send(mod, method_type, method_name), :visibility => visibility.to_sym)
+ end
+ end.flatten
+ end
- if file && RbxPath.is_core_path?(file)
- file = RbxPath.convert_path_to_full(file)
+ def all_methods_for(mod)
+ all_from_common(mod, :instance_method) + all_from_common(mod, :method)
+ end
+
+ def all_source_locations_by_popularity
+ return @all_source_locations_by_popularity if @all_source_locations_by_popularity
+
+ ims = all_methods_for(wrapped)
+
+ ims.reject! do |v|
+ begin
+ v.alias? || v.source_location.nil?
+ rescue Pry::RescuableException
+ true
+ end
end
- [file, line]
+ @all_source_locations_by_popularity = ims.group_by { |v| Array(v.source_location).first }.
+ sort_by { |k, v| -v.size }
+ end
+
+ def top_method_candidates
+ @top_method_candidtates ||= all_source_locations_by_popularity.map do |group|
+ sorted_by_lowest_line_number = group.last.sort_by(&:source_line)
+ best_candidate_for_group = sorted_by_lowest_line_number.first
+ end
end
- # FIXME: both methods below copied from Pry::Method
- def strip_leading_whitespace(text)
- Pry::Helpers::CommandHelpers.unindent(text)
+ def number_of_candidates
+ top_method_candidates.count
end
- # @param [String] comment
- # @return [String]
- def strip_leading_hash_and_whitespace_from_ruby_comments(comment)
- comment = comment.dup
- comment.gsub!(/\A\#+?$/, '')
- comment.gsub!(/^\s*#/, '')
- strip_leading_whitespace(comment)
+ def method_source_location_for_candidate(idx)
+ file, line = top_method_candidates[idx].source_location
+
+ if file && RbxPath.is_core_path?(file)
+ file = RbxPath.convert_path_to_full(file)
+ end
+
+ [file, line]
end
end

0 comments on commit b5d044e

Please sign in to comment.
Something went wrong with that request. Please try again.