summaryrefslogtreecommitdiff
path: root/lib/method_source.rb
blob: bdb00ed5e7105c907285cf32726b66cc5651932b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# (C) John Mair (banisterfiend) 2010
# MIT License

direc = File.dirname(__FILE__)

require 'stringio'
require "#{direc}/method_source/version"

module MethodSource

  # Helper method used to find end of method body
  # @param [String] code The string of Ruby code to check for
  # correctness
  # @return [Boolean] 
  def self.valid_expression?(code)
    suppress_stderr do
      RubyVM::InstructionSequence.new(code)
    end
  rescue Exception
    false
  else
    true
  end

  # Helper method used to suppress stderr output by the
  # `RubyVM::InstructionSequence` method
  # @yield The block where stderr is suppressed
  def self.suppress_stderr
    real_stderr, $stderr = $stderr, StringIO.new
    yield
  ensure
    $stderr = real_stderr
  end

  # Helper method responsible for opening source file and advancing to
  # the correct linenumber. Defined here to avoid polluting `Method`
  # class.
  # @param [Array] source_location The array returned by Method#source_location
  # @return [File] The opened source file
  def self.source_helper(source_location)
    return nil if !source_location.is_a?(Array)
    
    file_name, line = source_location
    file = File.open(file_name)
    (line - 1).times { file.readline }
    file
  end
  
  # This module is to be included by `Method` and `UnboundMethod` and
  # provides the `#source` functionality
  module MethodExtensions
    
    # Return the sourcecode for the method as a string
    # (This functionality is only supported in Ruby 1.9 and above)
    # @return [String] The method sourcecode as a string
    # @example
    #  Set.instance_method(:clear).source.display
    #  =>
    #     def clear
    #       @hash.clear
    #       self
    #     end
    def source
      file = nil
      
      if respond_to?(:source_location)
        file = MethodSource.source_helper(source_location)
        
        raise "Cannot locate source for this method: #{name}" if !file
      else
        raise "Method#source not supported by this Ruby version (#{RUBY_VERSION})"
      end

      code = ""
      loop do
        val = file.readline
        code += val
        
        return code if MethodSource.valid_expression?(code)
      end
      
    ensure
      file.close if file
    end
  end
end

class Method
  include MethodSource::MethodExtensions
end

class UnboundMethod
  include MethodSource::MethodExtensions
end