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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
# (C) John Mair (banisterfiend) 2011
# MIT License
direc = File.dirname(__FILE__)
require "#{direc}/method_source/version"
require "#{direc}/method_source/source_location"
require "#{direc}/method_source/code_helpers"
module MethodSource
extend MethodSource::CodeHelpers
# An Exception to mark errors that were raised trying to find the source from
# a given source_location.
#
class SourceNotFoundError < StandardError; end
# Helper method responsible for extracting method body.
# Defined here to avoid polluting `Method` class.
# @param [Array] source_location The array returned by Method#source_location
# @param [String] method_name
# @return [String] The method body
def self.source_helper(source_location, name=nil)
raise SourceNotFoundError, "Could not locate source for #{name}!" unless source_location
file, line = *source_location
expression_at(lines_for(file), line)
rescue SyntaxError => e
raise SourceNotFoundError, "Could not parse source for #{name}: #{e.message}"
end
# Helper method responsible for opening source file and buffering up
# the comments for a specified method. Defined here to avoid polluting
# `Method` class.
# @param [Array] source_location The array returned by Method#source_location
# @param [String] method_name
# @return [String] The comments up to the point of the method.
def self.comment_helper(source_location, name=nil)
raise SourceNotFoundError, "Could not locate source for #{name}!" unless source_location
file, line = *source_location
comment_describing(lines_for(file), line)
end
# Load a memoized copy of the lines in a file.
#
# @param [String] file_name
# @param [String] method_name
# @return [Array<String>] the contents of the file
# @raise [SourceNotFoundError]
def self.lines_for(file_name, name=nil)
@lines_for_file ||= {}
@lines_for_file[file_name] ||= File.readlines(file_name)
rescue Errno::ENOENT => e
raise SourceNotFoundError, "Could not load source for #{name}: #{e.message}"
end
# Clear cache.
def self.clear_cache
@lines_for_file = {}
end
# @deprecated — use MethodSource::CodeHelpers#complete_expression?
def self.valid_expression?(str)
complete_expression?(str)
rescue SyntaxError
false
end
# @deprecated — use MethodSource::CodeHelpers#expression_at
def self.extract_code(source_location)
source_helper(source_location)
end
# This module is to be included by `Method` and `UnboundMethod` and
# provides the `#source` functionality
module MethodExtensions
# We use the included hook to patch Method#source on rubinius.
# We need to use the included hook as Rubinius defines a `source`
# on Method so including a module will have no effect (as it's
# higher up the MRO).
# @param [Class] klass The class that includes the module.
def self.included(klass)
if klass.method_defined?(:source) && Object.const_defined?(:RUBY_ENGINE) &&
RUBY_ENGINE =~ /rbx/
klass.class_eval do
orig_source = instance_method(:source)
define_method(:source) do
begin
super
rescue
orig_source.bind(self).call
end
end
end
end
end
# Return the sourcecode for the method as a string
# @return [String] The method sourcecode as a string
# @raise SourceNotFoundException
#
# @example
# Set.instance_method(:clear).source.display
# =>
# def clear
# @hash.clear
# self
# end
def source
MethodSource.source_helper(source_location, defined?(name) ? name : inspect)
end
# Return the comments associated with the method as a string.
# @return [String] The method's comments as a string
# @raise SourceNotFoundException
#
# @example
# Set.instance_method(:clear).comment.display
# =>
# # Removes all elements and returns self.
def comment
MethodSource.comment_helper(source_location, defined?(name) ? name : inspect)
end
# Return the comments associated with the method class/module.
# @return [String] The method's comments as a string
# @raise SourceNotFoundException
#
# @example
# MethodSource::MethodExtensions.method(:included).module_comment
# =>
# # This module is to be included by `Method` and `UnboundMethod` and
# # provides the `#source` functionality
def class_comment
if self.respond_to?(:receiver)
class_inst_or_module = self.receiver
elsif self.respond_to?(:owner)
class_inst_or_module = self.owner
else
return comment
end
if class_inst_or_module.respond_to?(:name)
const_name = class_inst_or_module.name
else
const_name = class_inst_or_module.class.name
class_inst_or_module = class_inst_or_module.class
end
location = class_inst_or_module.const_source_location(const_name)
MethodSource.comment_helper(location, defined?(name) ? name : inspect)
end
alias module_comment class_comment
end
end
class Method
include MethodSource::SourceLocation::MethodExtensions
include MethodSource::MethodExtensions
end
class UnboundMethod
include MethodSource::SourceLocation::UnboundMethodExtensions
include MethodSource::MethodExtensions
end
class Proc
include MethodSource::SourceLocation::ProcExtensions
include MethodSource::MethodExtensions
end
|