Class: Pry::WrappedModule

Inherits:
Object show all
Includes:
CodeObject::Helpers, Helpers::BaseHelpers
Defined in:
lib/pry/wrapped_module.rb,
lib/pry/wrapped_module/candidate.rb

Defined Under Namespace

Classes: Candidate

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from CodeObject::Helpers

#c_method?, #c_module?, #command?, #module_with_yard_docs?, #real_method_object?

Methods included from Helpers::BaseHelpers

#colorize_code, #find_command, #heading, #highlight, #not_a_real_file?, #safe_send, #silence_warnings, #stagger_output, #use_ansi_codes?

Constructor Details

#initialize(mod) ⇒ WrappedModule

Returns a new instance of WrappedModule

Raises:

  • (ArgumentError)

    if the argument is not a Module


56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/pry/wrapped_module.rb', line 56

def initialize(mod)
  unless mod.is_a?(Module)
    raise ArgumentError, "Tried to initialize a WrappedModule with a " \
                         "non-module #{mod.inspect}"
  end

  @wrapped = mod
  @memoized_candidates = []
  @host_file_lines = nil
  @source = nil
  @source_location = nil
  @doc = nil
  @all_source_locations_by_popularity = nil
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object

Forward method invocations to the wrapped module


150
151
152
153
154
155
156
# File 'lib/pry/wrapped_module.rb', line 150

def method_missing(method_name, *args, &block)
  if wrapped.respond_to?(method_name)
    wrapped.send(method_name, *args, &block)
  else
    super
  end
end

Instance Attribute Details

#wrappedObject (readonly)

Returns the value of attribute wrapped


20
21
22
# File 'lib/pry/wrapped_module.rb', line 20

def wrapped
  @wrapped
end

Class Method Details

.from_str(mod_name, target = TOPLEVEL_BINDING) ⇒ Module?

Convert a string to a module.

Examples:

Pry::WrappedModule.from_str("Pry::Code")

29
30
31
32
33
# File 'lib/pry/wrapped_module.rb', line 29

def self.from_str(mod_name, target = TOPLEVEL_BINDING)
  Pry::WrappedModule.new(target.eval(mod_name)) if safe_to_evaluate?(mod_name, target)
rescue RescuableException
  nil
end

.safe_to_evaluate?(str, target) ⇒ Boolean (private)

We use this method to decide whether code is safe to eval. Method's are generally not, but everything else is. TODO: is just checking != "method" enough?? TODO: see duplication of this method in Pry::CodeObject


45
46
47
48
49
50
51
# File 'lib/pry/wrapped_module.rb', line 45

def safe_to_evaluate?(str, target)
  return true if str.strip == "self"
  return false if str =~ /%/

  kind = target.eval("defined?(#{str})")
  kind =~ /variable|constant/
end

Instance Method Details

#all_methods_for(mod) ⇒ Array<Pry::Method> (private)

Return all methods (instance methods and class methods) for a given module.


352
353
354
# File 'lib/pry/wrapped_module.rb', line 352

def all_methods_for(mod)
  Pry::Method.all_from_obj(mod, false) + Pry::Method.all_from_class(mod, false)
end

#all_relevant_methods_for(mod) ⇒ Array<Pry::Method> (private)

We only want methods that have a non-nil source_location. We also skip some spooky internal methods.


334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/pry/wrapped_module.rb', line 334

def all_relevant_methods_for(mod)
  methods = all_methods_for(mod).select(&:source_location)
    .reject { |x| method_defined_by_forwardable_module?(x) }

  return methods unless methods.empty?

  safe_send(mod, :constants).flat_map do |const_name|
    if (const = nested_module?(mod, const_name))
      all_relevant_methods_for(const)
    else
      []
    end
  end
end

#all_source_locations_by_popularityObject (private)

A helper method.


315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/pry/wrapped_module.rb', line 315

def all_source_locations_by_popularity
  return @all_source_locations_by_popularity if @all_source_locations_by_popularity

  ims = all_relevant_methods_for(wrapped).group_by do |v|
    Array(v.source_location).first
  end

  @all_source_locations_by_popularity = ims.sort_by do |path, methods|
    expanded = File.expand_path(path)
    load_order = $LOADED_FEATURES.index { |file| expanded.end_with?(file) }

    [-methods.size, load_order || (1.0 / 0.0)]
  end
end

#candidate(rank) ⇒ Pry::WrappedModule::Candidate

Return a candidate for this module of specified rank. A rank of 0 is equivalent to the 'primary candidate', which is the module definition with the highest number of methods. A rank of 1 is the module definition with the second highest number of methods, and so on. Module candidates are necessary as modules can be reopened multiple times and in multiple places in Ruby, the candidate API gives you access to the module definition representing each of those reopenings.

Raises:

  • (Pry::CommandError)

    If the rank is out of range. That is greater than number_of_candidates - 1.


239
240
241
# File 'lib/pry/wrapped_module.rb', line 239

def candidate(rank)
  @memoized_candidates[rank] ||= WrappedModule::Candidate.new(self, rank)
end

#candidatesEnumerator, Array

Note:

On JRuby 1.9 and higher, in certain conditions, this method chucks away its ability to be quick (when there are lots of monkey patches, like in Rails). However, it should be efficient enough on other rubies.

Returns on JRuby 1.9 and higher returns Array, on other rubies returns Enumerator


255
256
257
258
259
260
261
262
# File 'lib/pry/wrapped_module.rb', line 255

def candidates
  enum = Enumerator.new do |y|
    (0...number_of_candidates).each do |num|
      y.yield candidate(num)
    end
  end
  Helpers::Platform.jruby_19? ? enum.to_a : enum
end

#class?Boolean

Is this strictly a class?


126
127
128
# File 'lib/pry/wrapped_module.rb', line 126

def class?
  wrapped.instance_of?(Class)
end

#constants(inherit = true) ⇒ Object

Returns an array of the names of the constants accessible in the wrapped module. This avoids the problem of accidentally calling the singleton method Module.constants.


76
77
78
# File 'lib/pry/wrapped_module.rb', line 76

def constants(inherit = true)
  Module.instance_method(:constants).bind(@wrapped).call(inherit)
end

#docString

Returns documentation for the module. This documentation is for the primary candidate, if you would like documentation for other candidates use WrappedModule#candidate to select the candidate you're interested in.

Raises:


195
196
197
# File 'lib/pry/wrapped_module.rb', line 195

def doc
  @doc ||= primary_candidate.doc
end

#fileString? Also known as: source_file


176
177
178
# File 'lib/pry/wrapped_module.rb', line 176

def file
  Array(source_location).first
end

#lineFixnum? Also known as: source_line


183
184
185
# File 'lib/pry/wrapped_module.rb', line 183

def line
  Array(source_location).last
end

#lines_for_file(file) ⇒ Object (private)

memoized lines for file


376
377
378
379
380
381
382
383
384
385
# File 'lib/pry/wrapped_module.rb', line 376

def lines_for_file(file)
  @lines_for_file ||= {}

  @lines_for_file[file] ||=
    if file == Pry.eval_path
      Pry.line_buffer.drop(1)
    else
      File.readlines(file)
    end
end

#method_candidatesArray<Array<Pry::Method>> (private)


307
308
309
310
311
312
# File 'lib/pry/wrapped_module.rb', line 307

def method_candidates
  @method_candidates ||= all_source_locations_by_popularity.map do |group|
    methods_sorted_by_source_line = group.last.sort_by(&:source_line)
    [methods_sorted_by_source_line.first, methods_sorted_by_source_line.last]
  end
end

#method_defined_by_forwardable_module?(method) ⇒ Boolean (private)

Detect methods that are defined with def_delegator from the Forwardable module. We want to reject these methods as they screw up module extraction since the source_location for such methods points at forwardable.rb TODO: make this more robust as valid user-defined files called forwardable.rb are also skipped.


371
372
373
# File 'lib/pry/wrapped_module.rb', line 371

def method_defined_by_forwardable_module?(method)
  method.source_location.first =~ /forwardable\.rb/
end

#method_prefixObject

The prefix that would appear before methods defined on this class.

i.e. the "String." or "String#" in String.new and String#initialize.


85
86
87
88
89
90
91
92
93
94
95
# File 'lib/pry/wrapped_module.rb', line 85

def method_prefix
  if singleton_class?
    if Module === singleton_instance # rubocop:disable Style/CaseEquality
      "#{WrappedModule.new(singleton_instance).nonblank_name}."
    else
      "self."
    end
  else
    "#{nonblank_name}#"
  end
end

#module?Boolean

Is this strictly a module? (does not match classes)


120
121
122
# File 'lib/pry/wrapped_module.rb', line 120

def module?
  wrapped.instance_of?(Module)
end

#nested_module?(parent, name) ⇒ Boolean (private)


356
357
358
359
360
361
362
363
364
# File 'lib/pry/wrapped_module.rb', line 356

def nested_module?(parent, name)
  return if safe_send(parent, :autoload?, name)

  child = safe_send(parent, :const_get, name)
  return unless child.is_a?(Module)
  return unless safe_send(child, :name) == "#{safe_send(parent, :name)}::#{name}"

  child
end

#nonblank_nameString

The name of the Module if it has one, otherwise #Class:0xf00.


100
101
102
103
104
105
106
# File 'lib/pry/wrapped_module.rb', line 100

def nonblank_name
  if name.to_s == ""
    wrapped.inspect
  else
    name
  end
end

#number_of_candidatesFixnum


245
246
247
# File 'lib/pry/wrapped_module.rb', line 245

def number_of_candidates
  method_candidates.count
end

#primary_candidatePry::WrappedModule::Candidate (private)


297
298
299
# File 'lib/pry/wrapped_module.rb', line 297

def primary_candidate
  @primary_candidate ||= candidates.find(&:file) || candidate(0)
end

#respond_to_missing?(method_name, include_private = false) ⇒ Boolean


158
159
160
# File 'lib/pry/wrapped_module.rb', line 158

def respond_to_missing?(method_name, include_private = false)
  wrapped.respond_to?(method_name, include_private) || super
end

#singleton_class?Boolean

Is this a singleton class?


110
111
112
113
114
115
116
# File 'lib/pry/wrapped_module.rb', line 110

def singleton_class?
  if Pry::Method.safe_send(wrapped, :respond_to?, :singleton_class?)
    Pry::Method.safe_send(wrapped, :singleton_class?)
  else
    wrapped != Pry::Method.safe_send(wrapped, :ancestors).first
  end
end

#singleton_instanceObject

Get the instance associated with this singleton class.

Raises:

  • ArgumentError: tried to get instance of non singleton class


135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/pry/wrapped_module.rb', line 135

def singleton_instance
  unless singleton_class?
    raise ArgumentError, "tried to get instance of non singleton class"
  end

  if Helpers::Platform.jruby?
    wrapped.to_java.attached
  else
    @singleton_instance ||= ObjectSpace.each_object(wrapped).detect do |x|
      (class << x; self; end) == wrapped
    end
  end
end

#sourceString

Returns the source for the module. This source is for the primary candidate, if you would like source for other candidates use WrappedModule#candidate to select the candidate you're interested in.

Raises:


206
207
208
# File 'lib/pry/wrapped_module.rb', line 206

def source
  @source ||= primary_candidate.source
end

#source_locationArray<String, Fixnum>?

Retrieve the source location of a module. Return value is in same format as Method#source_location. If the source location cannot be found this method returns nil.


168
169
170
171
172
# File 'lib/pry/wrapped_module.rb', line 168

def source_location
  @source_location ||= primary_candidate.source_location
rescue Pry::RescuableException
  nil
end

#super(times = 1) ⇒ Pry::WrappedModule?


275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/pry/wrapped_module.rb', line 275

def super(times = 1)
  return self if times.zero?

  sup =
    if wrapped.is_a?(Class)
      ancestors.select { |v| v.is_a?(Class) }[times]
    else
      ancestors[times]
    end

  Pry::WrappedModule(sup) if sup
end

#yard_docString


223
224
225
# File 'lib/pry/wrapped_module.rb', line 223

def yard_doc
  YARD::Registry.at(name).docstring.to_s if yard_docs?
end

#yard_docs?Boolean


265
266
267
# File 'lib/pry/wrapped_module.rb', line 265

def yard_docs?
  !!(defined?(YARD) && YARD::Registry.at(name))
end

#yard_fileString


212
213
214
# File 'lib/pry/wrapped_module.rb', line 212

def yard_file
  YARD::Registry.at(name).file if yard_docs?
end

#yard_lineFixnum


218
219
220
# File 'lib/pry/wrapped_module.rb', line 218

def yard_line
  YARD::Registry.at(name).line if yard_docs?
end