method_introspection 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +48 -0
- data/lib/method_introspection/code_helpers.rb +177 -0
- data/lib/method_introspection/load_project.rb +23 -0
- data/lib/method_introspection/module_methods.rb +78 -0
- data/lib/method_introspection/source_location.rb +68 -0
- data/lib/method_introspection/version/version.rb +5 -0
- data/lib/method_introspection.rb +58 -0
- data/method_introspection.gemspec +40 -0
- data/test/test.rb +138 -0
- data/test/test_code_helpers.rb +42 -0
- data/test/test_helper.rb +98 -0
- metadata +57 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a281ed170dd1e993975cc0cdd4ea889caa72a286
|
4
|
+
data.tar.gz: e3916ce0e0c21b1f2ef4fe2006e5bf5f5676b41c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a38dca730b231aa479f30b2ffb65ed0ef62236cef7786ed95d5f0f616da9360a765f86509cd2a59e952d5b11387baf037059805a90584a653909b348767241d6
|
7
|
+
data.tar.gz: 13857fafe0c960a482abb0612406fb0782df8572a9c3c7f34c9a1fc665e77a7e95b841f3f7f8d8214c6714997815d94f9156255a14ee04b522c17b04ec5f8abc
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
method_introspection
|
2
|
+
====================
|
3
|
+
|
4
|
+
This is a simplified variant of showing comments and source code.
|
5
|
+
|
6
|
+
It is based on John Mair's (banisterfiend) gem called _method_source_.
|
7
|
+
|
8
|
+
The gem is fine - however had I found that I only required MRI support
|
9
|
+
for my own gems since that is what I am using.
|
10
|
+
|
11
|
+
Thus, a lot of the code was not useful for me since it would be based
|
12
|
+
on jruby or rubinius or also ruby 1.8. Hence why I stripped down and
|
13
|
+
put it into a separate gem.
|
14
|
+
|
15
|
+
Credit where credit is due and in this case it was banisterfiend's
|
16
|
+
initial work.
|
17
|
+
|
18
|
+
The license is thus the MIT license. Please refer to his gem called
|
19
|
+
method_source for a description of that gem.
|
20
|
+
|
21
|
+
_Introspect ruby methods at runtime_
|
22
|
+
|
23
|
+
Method comments can be obtained via the `comment` method.
|
24
|
+
|
25
|
+
This library is written in pure Ruby.
|
26
|
+
|
27
|
+
Examples: display method source
|
28
|
+
------------------------------
|
29
|
+
|
30
|
+
Set.instance_method(:merge).source.display
|
31
|
+
# =>
|
32
|
+
def merge(enum)
|
33
|
+
if enum.instance_of?(self.class)
|
34
|
+
@hash.update(enum.instance_variable_get(:@hash))
|
35
|
+
else
|
36
|
+
do_with_enum(enum) { |o| add(o) }
|
37
|
+
end
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
Example: display method comments
|
43
|
+
--------------------------------
|
44
|
+
|
45
|
+
Set.instance_method(:merge).comment.display
|
46
|
+
# =>
|
47
|
+
# Merges the elements of the given enumerable object to the set and
|
48
|
+
# returns self.
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module MethodIntrospection
|
2
|
+
|
3
|
+
module CodeHelpers
|
4
|
+
|
5
|
+
# ========================================================================= #
|
6
|
+
# === expression_at
|
7
|
+
#
|
8
|
+
# Retrieve the first expression starting on the given line of the
|
9
|
+
# given file.
|
10
|
+
#
|
11
|
+
# This is useful to get module or method source code.
|
12
|
+
#
|
13
|
+
# @param [Array<String>, File, String] file The file to parse, either as a File or as
|
14
|
+
# @param [Fixnum] line_number The line number at which to look.
|
15
|
+
# NOTE: The first line in a file is
|
16
|
+
# line 1!
|
17
|
+
#
|
18
|
+
# @param [Hash] options The optional configuration parameters.
|
19
|
+
# @option options [Boolean] :strict If set to true, then only completely
|
20
|
+
# valid expressions are returned. Otherwise heuristics are used to extract
|
21
|
+
# expressions that may have been valid inside an eval.
|
22
|
+
# @option options [Fixnum] :consume A number of lines to automatically
|
23
|
+
# consume (add to the expression buffer) without checking for validity.
|
24
|
+
# @return [String] The first complete expression
|
25
|
+
# @raise [SyntaxError] If the first complete expression can't be identified
|
26
|
+
# ========================================================================= #
|
27
|
+
def expression_at(
|
28
|
+
file,
|
29
|
+
line_number,
|
30
|
+
options = {}
|
31
|
+
)
|
32
|
+
options = {
|
33
|
+
:strict => false,
|
34
|
+
:consume => 0
|
35
|
+
}.merge!(options)
|
36
|
+
|
37
|
+
if file.is_a? Array
|
38
|
+
lines = file
|
39
|
+
else
|
40
|
+
lines = file.each_line.to_a
|
41
|
+
end
|
42
|
+
|
43
|
+
relevant_lines = lines[(line_number - 1)..-1] || []
|
44
|
+
|
45
|
+
extract_first_expression(relevant_lines, options[:consume])
|
46
|
+
rescue SyntaxError => e
|
47
|
+
raise if options[:strict]
|
48
|
+
|
49
|
+
begin
|
50
|
+
extract_first_expression(relevant_lines) { |code|
|
51
|
+
code.gsub(/\#\{.*?\}/, "temp")
|
52
|
+
}
|
53
|
+
rescue SyntaxError
|
54
|
+
raise e
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# ========================================================================= #
|
59
|
+
# === comment_describing
|
60
|
+
#
|
61
|
+
# Retrieve the comment describing the expression on the given line of
|
62
|
+
# the given file.
|
63
|
+
#
|
64
|
+
# This is useful to get module or method documentation.
|
65
|
+
#
|
66
|
+
# @param [Array<String>, File, String] file The file to parse, either as a File or as
|
67
|
+
# a String or an Array of lines.
|
68
|
+
# @param [Fixnum] line_number The line number at which to look.
|
69
|
+
# NOTE: The first line in a file is line 1!
|
70
|
+
# @return [String] The comment
|
71
|
+
# ========================================================================= #
|
72
|
+
def comment_describing(file, line_number)
|
73
|
+
lines = file.is_a?(Array) ? file : file.each_line.to_a
|
74
|
+
extract_last_comment(lines[0..(line_number - 2)])
|
75
|
+
end
|
76
|
+
|
77
|
+
# ========================================================================= #
|
78
|
+
# === complete_expression?
|
79
|
+
#
|
80
|
+
# Determine if a string of code is a complete Ruby expression.
|
81
|
+
#
|
82
|
+
# @param [String] code The code to validate.
|
83
|
+
# @return [Boolean] Whether or not the code is a complete Ruby expression.
|
84
|
+
# @raise [SyntaxError] Any SyntaxError that does not represent incompleteness.
|
85
|
+
# @example
|
86
|
+
# complete_expression?("class Hello") #=> false
|
87
|
+
# complete_expression?("class Hello; end") #=> true
|
88
|
+
# complete_expression?("class 123") #=> SyntaxError: unexpected tINTEGER
|
89
|
+
# ========================================================================= #
|
90
|
+
def complete_expression?(i)
|
91
|
+
old_verbose = $VERBOSE
|
92
|
+
$VERBOSE = nil
|
93
|
+
catch(:valid) {
|
94
|
+
eval("BEGIN{throw :valid}\n#{i}")
|
95
|
+
}
|
96
|
+
# Assert that a line which ends with a , or \ is incomplete.
|
97
|
+
i !~ /[,\\]\s*\z/
|
98
|
+
rescue IncompleteExpression
|
99
|
+
false
|
100
|
+
ensure
|
101
|
+
$VERBOSE = old_verbose
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# ========================================================================= #
|
107
|
+
# === extract_first_expression
|
108
|
+
#
|
109
|
+
# Get the first expression from the input.
|
110
|
+
#
|
111
|
+
# @param [Array<String>] lines
|
112
|
+
# @param [Fixnum] consume A number of lines to automatically
|
113
|
+
# consume (add to the expression buffer) without checking for validity.
|
114
|
+
# @yield a clean-up function to run before checking for complete_expression
|
115
|
+
# @return [String] a valid ruby expression
|
116
|
+
# @raise [SyntaxError]
|
117
|
+
# ========================================================================= #
|
118
|
+
def extract_first_expression(lines, consume = 0, &block)
|
119
|
+
code = consume.zero? ? '' : lines.slice!(0..(consume - 1)).join
|
120
|
+
lines.each { |entry|
|
121
|
+
code << entry
|
122
|
+
return code if complete_expression?(block ? block.call(code) : code)
|
123
|
+
}
|
124
|
+
raise SyntaxError, "unexpected $end"
|
125
|
+
end
|
126
|
+
|
127
|
+
# ========================================================================= #
|
128
|
+
# === extract_last_comment
|
129
|
+
#
|
130
|
+
# Get the last comment from the input.
|
131
|
+
#
|
132
|
+
# @param [Array<String>] lines
|
133
|
+
# @return [String]
|
134
|
+
# ========================================================================= #
|
135
|
+
def extract_last_comment(lines)
|
136
|
+
result = ''
|
137
|
+
lines.each { |line|
|
138
|
+
# ===================================================================== #
|
139
|
+
# Add any line that is a valid ruby comment but stop the moment
|
140
|
+
# we hit a non-comment line.
|
141
|
+
# ===================================================================== #
|
142
|
+
if (line =~ /^\s*#/) || (line =~ /^\s*$/)
|
143
|
+
result << line.lstrip
|
144
|
+
else
|
145
|
+
result.replace("")
|
146
|
+
end
|
147
|
+
}
|
148
|
+
return result
|
149
|
+
end
|
150
|
+
|
151
|
+
# ========================================================================= #
|
152
|
+
# === IncompleteExpression
|
153
|
+
#
|
154
|
+
# An exception matcher that matches only subsets of SyntaxErrors that can be
|
155
|
+
# fixed by adding more input to the buffer.
|
156
|
+
# ========================================================================= #
|
157
|
+
module IncompleteExpression
|
158
|
+
GENERIC_REGEXPS = [
|
159
|
+
/unexpected (\$end|end-of-file|end-of-input|END_OF_FILE)/, # mri, jruby, ruby-2.0, ironruby
|
160
|
+
/embedded document meets end of file/, # =begin
|
161
|
+
/unterminated (quoted string|string|regexp) meets end of file/, # "quoted string" is ironruby
|
162
|
+
/can not find the string ".*" anywhere before EOF/, # rbx and jruby
|
163
|
+
/missing 'end' for/, /expecting kWHEN/ # rbx
|
164
|
+
]
|
165
|
+
|
166
|
+
def self.===(ex)
|
167
|
+
return false unless SyntaxError === ex
|
168
|
+
case ex.message
|
169
|
+
when *GENERIC_REGEXPS
|
170
|
+
true
|
171
|
+
else
|
172
|
+
false
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end; end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MethodIntrospection # require 'method_source/project_base_directory.rb'
|
2
|
+
|
3
|
+
# ========================================================================= #
|
4
|
+
# === PROJECT_BASE_DIRECTORY
|
5
|
+
# ========================================================================= #
|
6
|
+
PROJECT_BASE_DIRECTORY = RbConfig::CONFIG['sitelibdir']+'/method_introspection/'
|
7
|
+
|
8
|
+
# ========================================================================= #
|
9
|
+
# === MethodIntrospection.project_base_dir?
|
10
|
+
# ========================================================================= #
|
11
|
+
def self.project_base_dir?
|
12
|
+
PROJECT_BASE_DIRECTORY
|
13
|
+
end
|
14
|
+
|
15
|
+
# ========================================================================= #
|
16
|
+
# Next we load up the various .rb files of that project.
|
17
|
+
# ========================================================================= #
|
18
|
+
require MethodIntrospection.project_base_dir?+'version/version.rb'
|
19
|
+
require MethodIntrospection.project_base_dir?+'source_location'
|
20
|
+
require MethodIntrospection.project_base_dir?+'code_helpers'
|
21
|
+
require MethodIntrospection.project_base_dir?+'module_methods'
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module MethodIntrospection
|
2
|
+
|
3
|
+
# ========================================================================= #
|
4
|
+
# === SourceNotFoundError
|
5
|
+
#
|
6
|
+
# An Exception to mark errors that were raised trying to find the
|
7
|
+
# source from a given source_location.
|
8
|
+
# ========================================================================= #
|
9
|
+
class SourceNotFoundError < StandardError; end
|
10
|
+
|
11
|
+
# ========================================================================= #
|
12
|
+
# === MethodIntrospection.raise_this
|
13
|
+
#
|
14
|
+
# Second argument is optional, and allows for a longer description.
|
15
|
+
# ========================================================================= #
|
16
|
+
def self.raise_this(name, optional_extra = nil)
|
17
|
+
result = "We could not locate the source for #{name}"
|
18
|
+
if optional_extra
|
19
|
+
result << ": #{optional_extra}"
|
20
|
+
else
|
21
|
+
result << '.'
|
22
|
+
end
|
23
|
+
raise SourceNotFoundError, result
|
24
|
+
end
|
25
|
+
|
26
|
+
# ========================================================================= #
|
27
|
+
# === MethodIntrospection.source_helper
|
28
|
+
#
|
29
|
+
# Helper method responsible for extracting the body of a method.
|
30
|
+
#
|
31
|
+
# Defined here to avoid polluting `Method` class.
|
32
|
+
# @param [Array] source_location The array returned by Method#source_location
|
33
|
+
# @param [String] method_name
|
34
|
+
# @return [String] The method body
|
35
|
+
# ========================================================================= #
|
36
|
+
def self.source_helper(source_location, name = nil)
|
37
|
+
raise_this(name) unless source_location
|
38
|
+
file, line = *source_location
|
39
|
+
expression_at(lines_for(file), line)
|
40
|
+
rescue SyntaxError => error
|
41
|
+
raise SourceNotFoundError,
|
42
|
+
"Could not parse source for #{name}: #{error.message}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# ========================================================================= #
|
46
|
+
# === MethodIntrospection.comment_helper
|
47
|
+
#
|
48
|
+
# Helper method responsible for opening source file and buffering up
|
49
|
+
# the comments for a specified method. Defined here to avoid polluting
|
50
|
+
# `Method` class.
|
51
|
+
# @param [Array] source_location The array returned by Method#source_location
|
52
|
+
# @param [String] method_name
|
53
|
+
# @return [String] The comments up to the point of the method.
|
54
|
+
# ========================================================================= #
|
55
|
+
def self.comment_helper(source_location, name = nil)
|
56
|
+
raise_this(name) unless source_location
|
57
|
+
file, line = *source_location
|
58
|
+
comment_describing(lines_for(file), line)
|
59
|
+
end
|
60
|
+
|
61
|
+
# ========================================================================= #
|
62
|
+
# === MethodIntrospection.lines_for
|
63
|
+
#
|
64
|
+
# Load a memoized copy of the lines in a file.
|
65
|
+
#
|
66
|
+
# @param [String] file_name
|
67
|
+
# @param [String] method_name
|
68
|
+
# @return [Array<String>] the contents of the file
|
69
|
+
# @raise [SourceNotFoundError]
|
70
|
+
# ========================================================================= #
|
71
|
+
def self.lines_for(file_name, name = nil)
|
72
|
+
@lines_for_file ||= {}
|
73
|
+
@lines_for_file[file_name] ||= File.readlines(file_name)
|
74
|
+
rescue Errno::ENOENT => e
|
75
|
+
raise_this(name, e.message)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module MethodIntrospection
|
2
|
+
module SourceLocation
|
3
|
+
module MethodExtensions
|
4
|
+
# ===================================================================== #
|
5
|
+
# === trace_func
|
6
|
+
# ===================================================================== #
|
7
|
+
def trace_func(event, file, line, id, binding, classname)
|
8
|
+
return unless event == 'call'
|
9
|
+
set_trace_func(nil)
|
10
|
+
@file, @line = file, line
|
11
|
+
raise :found
|
12
|
+
end; private :trace_func
|
13
|
+
end
|
14
|
+
|
15
|
+
module ProcExtensions
|
16
|
+
# Return the source location for a Proc (in implementations
|
17
|
+
# without Proc#source_location)
|
18
|
+
#
|
19
|
+
# @return [Array] A two element array. First element is the
|
20
|
+
# file, second element is the line in the file where the
|
21
|
+
# proc definition is found.
|
22
|
+
def source_location
|
23
|
+
self.to_s =~ /@(.*):(\d+)/
|
24
|
+
[$1, $2.to_i]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module UnboundMethodExtensions
|
29
|
+
# Return the source location of an instance method for Ruby 1.8.
|
30
|
+
#
|
31
|
+
# @return [Array] A two element array. First element is the
|
32
|
+
# file, second element is the line in the file where the
|
33
|
+
# method definition is found.
|
34
|
+
def source_location
|
35
|
+
klass = case owner
|
36
|
+
when Class
|
37
|
+
owner
|
38
|
+
when Module
|
39
|
+
method_owner = owner
|
40
|
+
Class.new { include(method_owner) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# deal with immediate values
|
44
|
+
case
|
45
|
+
when klass == Symbol
|
46
|
+
return :a.method(name).source_location
|
47
|
+
when klass == Fixnum
|
48
|
+
return 0.method(name).source_location
|
49
|
+
when klass == TrueClass
|
50
|
+
return true.method(name).source_location
|
51
|
+
when klass == FalseClass
|
52
|
+
return false.method(name).source_location
|
53
|
+
when klass == NilClass
|
54
|
+
return nil.method(name).source_location
|
55
|
+
end
|
56
|
+
begin
|
57
|
+
Object.instance_method(:method).bind(klass.allocate).call(name).source_location
|
58
|
+
rescue TypeError
|
59
|
+
# Assume we are dealing with a Singleton Class:
|
60
|
+
# 1. Get the instance object
|
61
|
+
# 2. Forward the source_location lookup to the instance
|
62
|
+
instance ||= ObjectSpace.each_object(owner).first
|
63
|
+
Object.instance_method(:method).bind(instance).call(name).source_location
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'method_introspection/load_project.rb'
|
2
|
+
|
3
|
+
module MethodIntrospection
|
4
|
+
|
5
|
+
extend MethodIntrospection::CodeHelpers
|
6
|
+
|
7
|
+
# This module is to be included by `Method` and `UnboundMethod` and
|
8
|
+
# provides the `#source` functionality
|
9
|
+
module MethodExtensions
|
10
|
+
# === source
|
11
|
+
#
|
12
|
+
# The method source() will return the sourcecode for the method as
|
13
|
+
# a String.
|
14
|
+
#
|
15
|
+
# @return [String] The method sourcecode as a string
|
16
|
+
# @raise SourceNotFoundException
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# Set.instance_method(:clear).source.display
|
20
|
+
# =>
|
21
|
+
# def clear
|
22
|
+
# @hash.clear
|
23
|
+
# self
|
24
|
+
# end
|
25
|
+
def source
|
26
|
+
MethodIntrospection.source_helper(source_location, defined?(name) ? name : inspect)
|
27
|
+
end
|
28
|
+
|
29
|
+
# === comment
|
30
|
+
#
|
31
|
+
# Return the comments associated with the method as a string.
|
32
|
+
# @return [String] The method's comments as a string
|
33
|
+
# @raise SourceNotFoundException
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# Set.instance_method(:clear).comment.display
|
37
|
+
# =>
|
38
|
+
# # Removes all elements and returns self.
|
39
|
+
def comment
|
40
|
+
MethodIntrospection.comment_helper(source_location, defined?(name) ? name : inspect)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Method
|
46
|
+
include MethodIntrospection::SourceLocation::MethodExtensions
|
47
|
+
include MethodIntrospection::MethodExtensions
|
48
|
+
end
|
49
|
+
|
50
|
+
class UnboundMethod
|
51
|
+
include MethodIntrospection::SourceLocation::UnboundMethodExtensions
|
52
|
+
include MethodIntrospection::MethodExtensions
|
53
|
+
end
|
54
|
+
|
55
|
+
class Proc
|
56
|
+
include MethodIntrospection::SourceLocation::ProcExtensions
|
57
|
+
include MethodIntrospection::MethodExtensions
|
58
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'method_introspection/version/version.rb'
|
3
|
+
|
4
|
+
DESCRIPTION = 'Allows us to inspect the source code for a method and the documentation.'
|
5
|
+
|
6
|
+
Gem::Specification.new { |s|
|
7
|
+
s.name = 'method_introspection'
|
8
|
+
s.version = MethodIntrospection::VERSION
|
9
|
+
|
10
|
+
s.authors = ['Robert A. Heiler']
|
11
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
12
|
+
s.email = 'shevegen@gmail.com'
|
13
|
+
s.files = %w(
|
14
|
+
README.md
|
15
|
+
lib/method_introspection.rb
|
16
|
+
lib/method_introspection/code_helpers.rb
|
17
|
+
lib/method_introspection/source_location.rb
|
18
|
+
lib/method_introspection/load_project.rb
|
19
|
+
lib/method_introspection/module_methods.rb
|
20
|
+
lib/method_introspection/version/version.rb
|
21
|
+
test/test.rb
|
22
|
+
test/test_code_helpers.rb
|
23
|
+
test/test_helper.rb
|
24
|
+
method_introspection.gemspec
|
25
|
+
)
|
26
|
+
s.homepage = 'http://shevegen.square7.ch/'
|
27
|
+
s.require_paths = ['lib']
|
28
|
+
s.summary = DESCRIPTION
|
29
|
+
s.description = DESCRIPTION
|
30
|
+
s.license = 'MIT'
|
31
|
+
s.test_files = [
|
32
|
+
'test/test.rb',
|
33
|
+
'test/test_code_helpers.rb',
|
34
|
+
'test/test_helper.rb'
|
35
|
+
]
|
36
|
+
|
37
|
+
s.required_ruby_version = '>= 2.2.2'
|
38
|
+
s.rubygems_version = '2.4.8'
|
39
|
+
|
40
|
+
}
|
data/test/test.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
direc = File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bacon'
|
5
|
+
require 'method_introspection/method_source.rb'
|
6
|
+
require "#{direc}/test_helper"
|
7
|
+
|
8
|
+
describe MethodIntrospection do
|
9
|
+
|
10
|
+
describe "source_location (testing 1.8 implementation)" do
|
11
|
+
it 'should return correct source_location for a method' do
|
12
|
+
method(:hello).source_location.first.should =~ /test_helper/
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should not raise for immediate instance methods' do
|
16
|
+
[Symbol, Fixnum, TrueClass, FalseClass, NilClass].each do |immediate_class|
|
17
|
+
lambda { immediate_class.instance_method(:to_s).source_location }.should.not.raise
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should not raise for immediate methods' do
|
22
|
+
[:a, 1, true, false, nil].each do |immediate|
|
23
|
+
lambda { immediate.method(:to_s).source_location }.should.not.raise
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
before do
|
29
|
+
@hello_module_source = " def hello; :hello_module; end\n"
|
30
|
+
@hello_singleton_source = "def $o.hello; :hello_singleton; end\n"
|
31
|
+
@hello_source = "def hello; :hello; end\n"
|
32
|
+
@hello_comment = "# A comment for hello\n# It spans two lines and is indented by 2 spaces\n"
|
33
|
+
@lambda_comment = "# This is a comment for MyLambda\n"
|
34
|
+
@lambda_source = "MyLambda = lambda { :lambda }\n"
|
35
|
+
@proc_source = "MyProc = Proc.new { :proc }\n"
|
36
|
+
@hello_instance_evaled_source = " def hello_\#{name}(*args)\n send_mesg(:\#{name}, *args)\n end\n"
|
37
|
+
@hello_instance_evaled_source_2 = " def \#{name}_two()\n if 44\n 45\n end\n end\n"
|
38
|
+
@hello_class_evaled_source = " def hello_\#{name}(*args)\n send_mesg(:\#{name}, *args)\n end\n"
|
39
|
+
@hi_module_evaled_source = " def hi_\#{name}\n @var = \#{name}\n end\n"
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should define methods on Method and UnboundMethod and Proc' do
|
43
|
+
Method.method_defined?(:source).should == true
|
44
|
+
UnboundMethod.method_defined?(:source).should == true
|
45
|
+
Proc.method_defined?(:source).should == true
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "Methods" do
|
49
|
+
it 'should return source for method' do
|
50
|
+
method(:hello).source.should == @hello_source
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should return source for a method defined in a module' do
|
54
|
+
M.instance_method(:hello).source.should == @hello_module_source
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should return source for a singleton method as an instance method' do
|
58
|
+
class << $o; self; end.instance_method(:hello).source.should == @hello_singleton_source
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should return source for a singleton method' do
|
62
|
+
$o.method(:hello).source.should == @hello_singleton_source
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should return a comment for method' do
|
66
|
+
method(:hello).comment.should == @hello_comment
|
67
|
+
end
|
68
|
+
|
69
|
+
# These tests fail because of http://jira.codehaus.org/browse/JRUBY-4576
|
70
|
+
unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
|
71
|
+
it 'should return source for an *_evaled method' do
|
72
|
+
M.method(:hello_name).source.should == @hello_instance_evaled_source
|
73
|
+
M.method(:name_two).source.should == @hello_instance_evaled_source_2
|
74
|
+
M.instance_method(:hello_name).source.should == @hello_class_evaled_source
|
75
|
+
M.instance_method(:hi_name).source.should == @hi_module_evaled_source
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should raise error for evaled methods that do not pass __FILE__ and __LINE__ + 1 as its arguments" do
|
80
|
+
lambda { M.instance_method(:name_three).source }.should.raise MethodSource::SourceNotFoundError
|
81
|
+
end
|
82
|
+
|
83
|
+
if !is_rbx?
|
84
|
+
it 'should raise for C methods' do
|
85
|
+
lambda { method(:puts).source }.should.raise MethodSource::SourceNotFoundError
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# if RUBY_VERSION =~ /1.9/ || is_rbx?
|
91
|
+
describe "Lambdas and Procs" do
|
92
|
+
it 'should return source for proc' do
|
93
|
+
MyProc.source.should == @proc_source
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should return an empty string if there is no comment' do
|
97
|
+
MyProc.comment.should == ''
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should return source for lambda' do
|
101
|
+
MyLambda.source.should == @lambda_source
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should return comment for lambda' do
|
105
|
+
MyLambda.comment.should == @lambda_comment
|
106
|
+
end
|
107
|
+
end
|
108
|
+
# end
|
109
|
+
describe "Comment tests" do
|
110
|
+
before do
|
111
|
+
@comment1 = "# a\n# b\n"
|
112
|
+
@comment2 = "# a\n# b\n"
|
113
|
+
@comment3 = "# a\n#\n# b\n"
|
114
|
+
@comment4 = "# a\n# b\n"
|
115
|
+
@comment5 = "# a\n# b\n# c\n# d\n"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should correctly extract multi-line comments" do
|
119
|
+
method(:comment_test1).comment.should == @comment1
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should correctly strip leading whitespace before comments" do
|
123
|
+
method(:comment_test2).comment.should == @comment2
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should keep empty comment lines" do
|
127
|
+
method(:comment_test3).comment.should == @comment3
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should ignore blank lines between comments" do
|
131
|
+
method(:comment_test4).comment.should == @comment4
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should align all comments to same indent level" do
|
135
|
+
method(:comment_test5).comment.should == @comment5
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'method_introspection'
|
2
|
+
|
3
|
+
describe MethodSource::CodeHelpers do
|
4
|
+
before {
|
5
|
+
@tester = Object.new.extend(MethodSource::CodeHelpers)
|
6
|
+
}
|
7
|
+
[
|
8
|
+
["p = '", "'"],
|
9
|
+
["def", 'a', "(); end"],
|
10
|
+
["p = <<FOO", "lots", "and", "lots of", "foo", "FOO"],
|
11
|
+
["[", ":lets,", "'list',", "[/nested/", "], things ]"],
|
12
|
+
["abc =~ /hello", "/"],
|
13
|
+
["issue = %W/", "343/"],
|
14
|
+
["pouts(<<HI, 'foo", "bar", "HI", "baz')"],
|
15
|
+
["=begin", "no-one uses this syntax anymore...", "=end"],
|
16
|
+
["puts 1, 2,", "3"],
|
17
|
+
["puts 'hello'\\", "'world'"]
|
18
|
+
].each do |lines|
|
19
|
+
it "should not raise an error on broken lines: #{lines.join("\\n")}" do
|
20
|
+
1.upto(lines.size - 1) do |i|
|
21
|
+
@tester.complete_expression?(lines[0...i].join("\n") + "\n").should == false
|
22
|
+
end
|
23
|
+
@tester.complete_expression?(lines.join("\n")).should == true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
[
|
28
|
+
["end"],
|
29
|
+
["puts )("],
|
30
|
+
["1 1"],
|
31
|
+
["puts :"]
|
32
|
+
] + (RbConfig::CONFIG['ruby_install_name'] == 'rbx' ? [] : [
|
33
|
+
["def", "method(1"], # in this case the syntax error is "expecting ')'".
|
34
|
+
["o = Object.new.tap{ def o.render;","'MEH'", "}"] # in this case the syntax error is "expecting keyword_end".
|
35
|
+
]).compact.each do |foo|
|
36
|
+
it "should raise an error on invalid syntax like #{foo.inspect}" do
|
37
|
+
lambda{
|
38
|
+
@tester.complete_expression?(foo.join("\n"))
|
39
|
+
}.should.raise(SyntaxError)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
def is_rbx?
|
2
|
+
defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/
|
3
|
+
end
|
4
|
+
|
5
|
+
def jruby?
|
6
|
+
defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /jruby/
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
module M
|
11
|
+
def hello; :hello_module; end
|
12
|
+
end
|
13
|
+
|
14
|
+
$o = Object.new
|
15
|
+
def $o.hello; :hello_singleton; end
|
16
|
+
|
17
|
+
# A comment for hello
|
18
|
+
|
19
|
+
# It spans two lines and is indented by 2 spaces
|
20
|
+
def hello; :hello; end
|
21
|
+
|
22
|
+
# a
|
23
|
+
# b
|
24
|
+
def comment_test1; end
|
25
|
+
|
26
|
+
# a
|
27
|
+
# b
|
28
|
+
def comment_test2; end
|
29
|
+
|
30
|
+
# a
|
31
|
+
#
|
32
|
+
# b
|
33
|
+
def comment_test3; end
|
34
|
+
|
35
|
+
# a
|
36
|
+
|
37
|
+
# b
|
38
|
+
def comment_test4; end
|
39
|
+
|
40
|
+
|
41
|
+
# a
|
42
|
+
# b
|
43
|
+
# c
|
44
|
+
# d
|
45
|
+
def comment_test5; end
|
46
|
+
|
47
|
+
# This is a comment for MyLambda
|
48
|
+
MyLambda = lambda { :lambda }
|
49
|
+
MyProc = Proc.new { :proc }
|
50
|
+
|
51
|
+
|
52
|
+
name = "name"
|
53
|
+
|
54
|
+
M.instance_eval <<-METHOD, __FILE__, __LINE__ + 1
|
55
|
+
def hello_#{name}(*args)
|
56
|
+
send_mesg(:#{name}, *args)
|
57
|
+
end
|
58
|
+
METHOD
|
59
|
+
|
60
|
+
M.class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
61
|
+
def hello_#{name}(*args)
|
62
|
+
send_mesg(:#{name}, *args)
|
63
|
+
end
|
64
|
+
METHOD
|
65
|
+
|
66
|
+
# module_eval to DRY code up
|
67
|
+
#
|
68
|
+
M.module_eval <<-METHOD, __FILE__, __LINE__ + 1
|
69
|
+
|
70
|
+
# module_eval is used here
|
71
|
+
#
|
72
|
+
def hi_#{name}
|
73
|
+
@var = #{name}
|
74
|
+
end
|
75
|
+
METHOD
|
76
|
+
|
77
|
+
# case where 2 methods are defined inside an _eval block
|
78
|
+
#
|
79
|
+
M.instance_eval <<EOF, __FILE__, __LINE__ + 1
|
80
|
+
|
81
|
+
def #{name}_one()
|
82
|
+
if 43
|
83
|
+
44
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def #{name}_two()
|
89
|
+
if 44
|
90
|
+
45
|
91
|
+
end
|
92
|
+
end
|
93
|
+
EOF
|
94
|
+
|
95
|
+
# class_eval without filename and lineno + 1 parameter
|
96
|
+
|
97
|
+
M.class_eval "def #{name}_three; @tempfile.#{name}; end"
|
98
|
+
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: method_introspection
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Robert A. Heiler
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-20 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Allows us to inspect the source code for a method and the documentation.
|
14
|
+
email: shevegen@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- README.md
|
20
|
+
- lib/method_introspection.rb
|
21
|
+
- lib/method_introspection/code_helpers.rb
|
22
|
+
- lib/method_introspection/load_project.rb
|
23
|
+
- lib/method_introspection/module_methods.rb
|
24
|
+
- lib/method_introspection/source_location.rb
|
25
|
+
- lib/method_introspection/version/version.rb
|
26
|
+
- method_introspection.gemspec
|
27
|
+
- test/test.rb
|
28
|
+
- test/test_code_helpers.rb
|
29
|
+
- test/test_helper.rb
|
30
|
+
homepage: http://shevegen.square7.ch/
|
31
|
+
licenses:
|
32
|
+
- MIT
|
33
|
+
metadata: {}
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 2.2.2
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
requirements: []
|
49
|
+
rubyforge_project:
|
50
|
+
rubygems_version: 2.4.5.1
|
51
|
+
signing_key:
|
52
|
+
specification_version: 4
|
53
|
+
summary: Allows us to inspect the source code for a method and the documentation.
|
54
|
+
test_files:
|
55
|
+
- test/test.rb
|
56
|
+
- test/test_code_helpers.rb
|
57
|
+
- test/test_helper.rb
|