class_source 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/class_source/collator.rb +80 -0
- data/lib/class_source/declarations.rb +21 -0
- data/lib/class_source/guesser.rb +25 -0
- data/lib/class_source/index.rb +41 -0
- data/lib/class_source/locator.rb +74 -0
- data/lib/class_source/method_index.rb +72 -0
- data/lib/class_source/version.rb +3 -0
- data/lib/class_source.rb +13 -0
- data/spec/class_source_spec.rb +223 -0
- data/spec/fixtures/class_methods_class.rb +5 -0
- data/spec/fixtures/cloned_class.rb +5 -0
- data/spec/fixtures/duplicated_class.rb +5 -0
- data/spec/fixtures/dynamic_class.rb +14 -0
- data/spec/fixtures/eval_class.rb +11 -0
- data/spec/fixtures/example_code.rb +10 -0
- data/spec/fixtures/nested_example.rb +5 -0
- data/spec/fixtures/no_methods_class.rb +5 -0
- data/spec/fixtures/outer_class.rb +22 -0
- data/spec/fixtures/protected_method_class.rb +19 -0
- data/spec/fixtures/re_opened_class.rb +16 -0
- data/spec/fixtures/re_opened_class_2.rb +11 -0
- data/spec/fixtures/side_effect_class.rb +11 -0
- data/spec/fixtures/simple_class.rb +6 -0
- data/spec/fixtures/sub_class.rb +10 -0
- data/spec/fixtures/unnested_child.rb +3 -0
- data/spec/spec_helper.rb +47 -0
- metadata +113 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'ruby_parser'
|
2
|
+
|
3
|
+
module ClassSource
|
4
|
+
class Collator
|
5
|
+
def initialize(target_class, index)
|
6
|
+
@klass = target_class
|
7
|
+
@source = index
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_hash(options = {})
|
11
|
+
full_sources = @source.locations(options).inject({}) do |results, location|
|
12
|
+
results[ location ] = source_helper(location)
|
13
|
+
results
|
14
|
+
end
|
15
|
+
|
16
|
+
return full_sources unless options[:include_nested] == false
|
17
|
+
full_sources.inject({}) do |clean_sources, (location, source)|
|
18
|
+
if nested_class_line_ranges[location.first]
|
19
|
+
complete_file = full_file(location)
|
20
|
+
target_range = (location.last - 1)..(location.last + source.lines.count - 2)
|
21
|
+
clean_sources[location] = complete_file.lines.to_a.select.with_index do |line, index|
|
22
|
+
target_range.include?(index) &&
|
23
|
+
nested_class_line_ranges[location.first].all? { |range| !range.include?(index) }
|
24
|
+
end.join("")
|
25
|
+
else
|
26
|
+
clean_sources[location] = source
|
27
|
+
end
|
28
|
+
clean_sources
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def nested_class_line_ranges
|
33
|
+
nested_classes = @klass.constants.select { |c| @klass.const_get(c).is_a?(Class) }.map {|c| @klass.const_get(c) }
|
34
|
+
return @nested_class_ranges if @nested_class_ranges
|
35
|
+
@nested_class_ranges = {}
|
36
|
+
nested_classes.each do |klass|
|
37
|
+
# (klass.source_location.last-1)..(klass.source_location.last + klass.source.lines.count - 2)
|
38
|
+
klass.__source__.all.each do |(file, line), source|
|
39
|
+
@nested_class_ranges[file] ||= []
|
40
|
+
@nested_class_ranges[file] << ((line - 1)..(line + source.lines.count - 2))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@nested_class_ranges
|
45
|
+
end
|
46
|
+
|
47
|
+
def full_file(location)
|
48
|
+
File.read(location.first)
|
49
|
+
end
|
50
|
+
|
51
|
+
# source_helper and valid_expression? are lifted from method_source
|
52
|
+
# (c) 2011 John Mair (banisterfiend)
|
53
|
+
def source_helper(source_location)
|
54
|
+
return nil if !source_location.is_a?(Array)
|
55
|
+
|
56
|
+
file_name, line = source_location
|
57
|
+
File.open(file_name) do |file|
|
58
|
+
(line - 1).times { file.readline }
|
59
|
+
|
60
|
+
code = ""
|
61
|
+
loop do
|
62
|
+
val = file.readline
|
63
|
+
code << val
|
64
|
+
|
65
|
+
return code if valid_expression?(code)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def valid_expression?(code)
|
72
|
+
RubyParser.new.parse(code)
|
73
|
+
rescue Racc::ParseError, SyntaxError
|
74
|
+
false
|
75
|
+
else
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ClassSource
|
2
|
+
class Declarations
|
3
|
+
def self.[](key)
|
4
|
+
@declarations ||= {}
|
5
|
+
@declarations[key]
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.add(klass_name, locations)
|
9
|
+
@declarations ||= {}
|
10
|
+
@declarations[klass_name] ||= []
|
11
|
+
@declarations[klass_name] += locations
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.save(declarations)
|
15
|
+
declarations.each do |klass_name, locations|
|
16
|
+
add(klass_name, locations)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ClassSource
|
2
|
+
class Guesser
|
3
|
+
def initialize(klass, source_files)
|
4
|
+
@source_files = source_files
|
5
|
+
@klass = klass
|
6
|
+
end
|
7
|
+
|
8
|
+
def locations
|
9
|
+
return if @source_files.empty?
|
10
|
+
@source_files.map do |file|
|
11
|
+
[file, find_module_name(File.read(file))]
|
12
|
+
end.select { |result| !result.last.nil? }
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def find_module_name(text)
|
18
|
+
submodule_name = @klass.name.split("::").last
|
19
|
+
text.lines.each.with_index do |line, index|
|
20
|
+
return index + 1 if line.match /\b#{submodule_name}\b/
|
21
|
+
end
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ClassSource
|
2
|
+
class Index
|
3
|
+
def initialize(target_class, options = {})
|
4
|
+
@target_class = target_class
|
5
|
+
@options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s(options={})
|
9
|
+
all(options).values.join("")
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(value)
|
13
|
+
to_s == value
|
14
|
+
end
|
15
|
+
|
16
|
+
def all(options={})
|
17
|
+
@collator ||= Collator.new(@target_class, self).to_hash(options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def locations(options={})
|
21
|
+
locator.to_a
|
22
|
+
end
|
23
|
+
|
24
|
+
def methods
|
25
|
+
@method_details ||= MethodIndex.new(@target_class)
|
26
|
+
end
|
27
|
+
|
28
|
+
def class_methods
|
29
|
+
methods.klass
|
30
|
+
end
|
31
|
+
|
32
|
+
def locator
|
33
|
+
@locator ||= Locator.new(@target_class, @options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def files
|
37
|
+
locator.files
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module ClassSource
|
5
|
+
class Locator
|
6
|
+
def initialize(target_class, options={})
|
7
|
+
@klass = target_class
|
8
|
+
@options=options
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_a
|
12
|
+
source_locations
|
13
|
+
end
|
14
|
+
|
15
|
+
def methods
|
16
|
+
MethodIndex.new(@klass)
|
17
|
+
end
|
18
|
+
|
19
|
+
def files(options={})
|
20
|
+
@source_files ||= methods.locations.map(&:first).uniq
|
21
|
+
return @source_files + [@options[:file]] if @options[:file]
|
22
|
+
@source_files
|
23
|
+
end
|
24
|
+
|
25
|
+
def source_locations(options={})
|
26
|
+
return @locations if @locations
|
27
|
+
t = Tempfile.new('class_creation_events')
|
28
|
+
fork do
|
29
|
+
declarations = files(options).inject({}) do |declarations, source_file|
|
30
|
+
trace_declarations(source_file, declarations)
|
31
|
+
end
|
32
|
+
YAML.dump(declarations, t)
|
33
|
+
end
|
34
|
+
Process.wait
|
35
|
+
Declarations.save YAML.load_file(t.path)
|
36
|
+
t.close
|
37
|
+
@locations = if !Declarations[@klass.name].nil?
|
38
|
+
Declarations[@klass.name].uniq
|
39
|
+
else
|
40
|
+
Guesser.new(@klass, files).locations || []
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def trace_declarations(source_file, declarations)
|
45
|
+
set_trace_func lambda { |event, file, line, id, binding, classname|
|
46
|
+
defined_class = standard_class_declared(event, binding) || dynamic_class_declared(id, classname, file, line)
|
47
|
+
break unless defined_class
|
48
|
+
defined_class_name = defined_class.is_a?(String) ? defined_class : defined_class.name
|
49
|
+
declarations[defined_class_name] ||= []
|
50
|
+
declarations[defined_class_name] << [ file, line ]
|
51
|
+
}
|
52
|
+
silence_warnings { load source_file }
|
53
|
+
declarations
|
54
|
+
end
|
55
|
+
|
56
|
+
def dynamic_class_declared(id, classname, file, line)
|
57
|
+
return unless id == :new && classname == Class
|
58
|
+
File.read(file).lines.to_a[line-1][/[A-Z][\w_:]*/, 0]
|
59
|
+
end
|
60
|
+
|
61
|
+
def standard_class_declared(event, binding)
|
62
|
+
return unless event == 'class'
|
63
|
+
event_class = eval( "Module.nesting", binding )
|
64
|
+
event_class.first
|
65
|
+
end
|
66
|
+
|
67
|
+
def silence_warnings
|
68
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
69
|
+
yield
|
70
|
+
ensure
|
71
|
+
$VERBOSE = old_verbose
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ClassSource
|
2
|
+
class MethodIndex
|
3
|
+
def initialize(target_class)
|
4
|
+
@target_class = target_class
|
5
|
+
end
|
6
|
+
|
7
|
+
def locations
|
8
|
+
@locations ||= (unique.map do |m|
|
9
|
+
@target_class.instance_method(m).source_location
|
10
|
+
end + klass.unique.map do |m|
|
11
|
+
@target_class.method(m).source_location
|
12
|
+
end).compact
|
13
|
+
end
|
14
|
+
|
15
|
+
def unique
|
16
|
+
uniquely_named_methods = all(:include_inherited_methods => false)
|
17
|
+
overridden_methods = (all - uniquely_named_methods).select do |m|
|
18
|
+
@target_class.instance_method(m).source_location != @target_class.superclass.instance_method(m).source_location
|
19
|
+
end
|
20
|
+
overridden_methods + uniquely_named_methods
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def all(options={})
|
25
|
+
include_inherited_methods = options.has_key?(:include_inherited_methods) ? options[:include_inherited_methods] : true
|
26
|
+
target = options[:target] || @target_class
|
27
|
+
target.public_instance_methods(include_inherited_methods) +
|
28
|
+
target.private_instance_methods(include_inherited_methods) +
|
29
|
+
target.protected_instance_methods(include_inherited_methods)
|
30
|
+
end
|
31
|
+
|
32
|
+
def klass
|
33
|
+
ClassMethodIndex.new(@target_class)
|
34
|
+
end
|
35
|
+
|
36
|
+
class ClassMethodIndex
|
37
|
+
def initialize(target_class)
|
38
|
+
@target_class = target_class
|
39
|
+
end
|
40
|
+
|
41
|
+
def unique
|
42
|
+
uniquely_named + overridden - extended
|
43
|
+
end
|
44
|
+
|
45
|
+
def extended
|
46
|
+
@target_class.singleton_class.ancestors.map do |mod|
|
47
|
+
mod.instance_methods.select { |m| mod.instance_method(m).source_location == @target_class.method(m).source_location }
|
48
|
+
end.flatten
|
49
|
+
end
|
50
|
+
|
51
|
+
def uniquely_named
|
52
|
+
@target_class.singleton_methods(false)
|
53
|
+
end
|
54
|
+
|
55
|
+
def overridden
|
56
|
+
(@target_class.methods - uniquely_named).select do |m|
|
57
|
+
!ancestral_sources(m).include?(@target_class.method(m).source_location)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def ancestral_sources(method)
|
62
|
+
superclasses.map { |mod| mod.respond_to?(method) && mod.method(method).source_location }.compact
|
63
|
+
end
|
64
|
+
|
65
|
+
def superclasses
|
66
|
+
@target_class.ancestors - [@target_class]
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
data/lib/class_source.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'class_source/declarations'
|
2
|
+
require 'class_source/index'
|
3
|
+
require 'class_source/method_index'
|
4
|
+
require 'class_source/locator'
|
5
|
+
require 'class_source/guesser'
|
6
|
+
require 'class_source/collator'
|
7
|
+
|
8
|
+
module ClassSource
|
9
|
+
def __source__(options={})
|
10
|
+
Index.new(self, options)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,223 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ClassSource do
|
4
|
+
after { test_unload_all }
|
5
|
+
|
6
|
+
describe "for a simple class with only an initializer" do
|
7
|
+
before do
|
8
|
+
test_load 'SimpleClass'
|
9
|
+
end
|
10
|
+
it "knows the unique methods of the class" do
|
11
|
+
SimpleClass.__source__.methods.unique.should == [:initialize]
|
12
|
+
end
|
13
|
+
|
14
|
+
it "knows the method location for each of the unique methods of the class" do
|
15
|
+
SimpleClass.__source__.methods.locations.should == [[fixtures_path(:simple_class), 4]]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can pinpoint the opening of a simple class" do
|
19
|
+
SimpleClass.__source__.locations.should == [[fixtures_path(:simple_class), 3]]
|
20
|
+
end
|
21
|
+
|
22
|
+
it "can return the full source of a simple class" do
|
23
|
+
SimpleClass.__source__.should == File.read(fixtures_path(:simple_class)).lines.to_a[2..-1].join("")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "for a class with public, private and protected methods" do
|
28
|
+
before { test_load 'ProtectedMethodClass' }
|
29
|
+
|
30
|
+
it "knows the unique methods of the class" do
|
31
|
+
ProtectedMethodClass.__source__.methods.unique.should =~ [:initialize, :talk, :think, :whisper]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "knows the method location for each of the unique methods of the class" do
|
35
|
+
ProtectedMethodClass.__source__.methods.locations.should =~ [
|
36
|
+
[fixtures_path(:protected_method_class), 2],
|
37
|
+
[fixtures_path(:protected_method_class), 6],
|
38
|
+
[fixtures_path(:protected_method_class), 11],
|
39
|
+
[fixtures_path(:protected_method_class), 16]
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
it "can pinpoint the opening" do
|
44
|
+
ProtectedMethodClass.__source__.locations.should == [[fixtures_path(:protected_method_class), 1]]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "can return the full source" do
|
48
|
+
ProtectedMethodClass.__source__.should == File.read(fixtures_path(:protected_method_class)).lines.to_a[0..-1].join("")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "for a subclass of a user class" do
|
53
|
+
before { test_load 'ProtectedMethodClass', 'SubClass' }
|
54
|
+
|
55
|
+
it "knows the unique methods of the class" do
|
56
|
+
SubClass.__source__.methods.unique.should =~ [:talk, :think]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "knows the method location for each of the unique methods of the class" do
|
60
|
+
SubClass.__source__.methods.locations.should =~ [
|
61
|
+
[fixtures_path(:sub_class), 2],
|
62
|
+
[fixtures_path(:sub_class), 6]
|
63
|
+
]
|
64
|
+
end
|
65
|
+
|
66
|
+
it "can pinpoint the opening" do
|
67
|
+
SubClass.__source__.locations.should == [[fixtures_path(:sub_class), 1]]
|
68
|
+
end
|
69
|
+
|
70
|
+
it "can return the full source" do
|
71
|
+
SubClass.__source__.should == File.read(fixtures_path(:sub_class)).lines.to_a[0..-1].join("")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "for a nested class" do
|
76
|
+
before { test_load 'OuterClass' }
|
77
|
+
it "can return the full source" do
|
78
|
+
OuterClass::NestedClass.__source__.should == File.read(fixtures_path(:outer_class)).lines.to_a[12..16].join("")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "for a class with no unique methods" do
|
83
|
+
before { test_load 'NoMethodsClass' }
|
84
|
+
it "can return the full source if you pass a source file" do
|
85
|
+
NoMethodsClass.__source__(:file => fixtures_path(:no_methods_class)).should == File.read(fixtures_path(:no_methods_class)).lines.to_a[0..4].join("")
|
86
|
+
end
|
87
|
+
describe "for a nested class with no unique methods" do
|
88
|
+
it "can return the full source" do
|
89
|
+
NoMethodsClass::NestedClass.__source__(:file => fixtures_path(:no_methods_class)).should == File.read(fixtures_path(:no_methods_class)).lines.to_a[1..3].join("")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "for an outer class" do
|
95
|
+
before { test_load 'OuterClass' }
|
96
|
+
it "can return the full source" do
|
97
|
+
OuterClass.__source__.should == File.read(fixtures_path(:outer_class)).lines.to_a[0..-1].join("")
|
98
|
+
end
|
99
|
+
|
100
|
+
it "can also return the source minus any nested classes" do
|
101
|
+
source_lines = File.read(fixtures_path(:outer_class)).lines.to_a
|
102
|
+
non_nested_lines = (source_lines[0..3] + source_lines[8..11] + source_lines[17..-1]).join("")
|
103
|
+
OuterClass.__source__.to_s(:include_nested => false).should == non_nested_lines
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "for dynamically defined classes" do
|
108
|
+
before do
|
109
|
+
test_load 'DynamicClass'
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should have a single source location" do
|
113
|
+
DynamicClass.__source__.should have(1).locations
|
114
|
+
end
|
115
|
+
|
116
|
+
it "can return the full source" do
|
117
|
+
DynamicClass.__source__.should == File.read(fixtures_path(:dynamic_class)).lines.to_a[0..4].join("")
|
118
|
+
end
|
119
|
+
|
120
|
+
it "can return the source for a class named later in the file" do
|
121
|
+
LateNamedDynamicClass.send :extend, ClassSource
|
122
|
+
LateNamedDynamicClass.__source__.should == File.read(fixtures_path(:dynamic_class)).lines.to_a[11]
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "with no methods" do
|
126
|
+
it "knows the source" do
|
127
|
+
MethodlessDynamicClass.send :extend, ClassSource
|
128
|
+
MethodlessDynamicClass.__source__.should == "MethodlessDynamicClass = Class.new\n"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "for duplicated classes" do
|
134
|
+
before { test_load 'DuplicatedClass' }
|
135
|
+
it "should point to the duplication point" do
|
136
|
+
DuplicateClass.__source__.locations.should == [
|
137
|
+
[fixtures_path(:duplicated_class), 5]
|
138
|
+
]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "for cloned classes" do
|
143
|
+
before { test_load 'ClonedClass' }
|
144
|
+
it "should point to the cloning point" do
|
145
|
+
CloneClass.__source__.locations.should == [
|
146
|
+
[fixtures_path(:cloned_class), 5]
|
147
|
+
]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "for classes defined within eval" do
|
152
|
+
before { test_load 'EvalClass' }
|
153
|
+
it "should point to the evaluation point" do
|
154
|
+
EvalClass.__source__.locations.should == [
|
155
|
+
[fixtures_path(:eval_class), 11]
|
156
|
+
]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "for classes that are reopened in separate files" do
|
161
|
+
before { test_load 'ReOpenedClass' }
|
162
|
+
it "should have more than one source file" do
|
163
|
+
ReOpenedClass.__source__.files.length.should == 2
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should have more than one source location" do
|
167
|
+
ReOpenedClass.__source__.locations.length.should == 2
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should have the full source in its source" do
|
171
|
+
ReOpenedClass.__source__.all.should == {
|
172
|
+
[fixtures_path(:re_opened_class), 3] => File.read(fixtures_path(:re_opened_class)).lines.to_a[2..-1].join(""),
|
173
|
+
[fixtures_path(:re_opened_class_2), 1] => File.read(fixtures_path(:re_opened_class_2))
|
174
|
+
}
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should be able to display without nested classes if needed" do
|
178
|
+
reopened_source = File.read(fixtures_path(:re_opened_class)).lines.to_a
|
179
|
+
reopened_source_2 = File.read(fixtures_path(:re_opened_class_2)).lines.to_a
|
180
|
+
ReOpenedClass.__source__.all(:include_nested => false).should == {
|
181
|
+
[fixtures_path(:re_opened_class), 3] => (reopened_source[2..6] + reopened_source[15..-1]).join(""),
|
182
|
+
[fixtures_path(:re_opened_class_2), 1] => (reopened_source_2[0..4] + reopened_source_2[10..-1]).join("")
|
183
|
+
}
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "for a nested class that is reopened within the parent" do
|
188
|
+
before { test_load 'ReOpenedClass' }
|
189
|
+
it "should have more than one source location" do
|
190
|
+
ReOpenedClass::NestedClass.__source__.all.should == {
|
191
|
+
[fixtures_path(:re_opened_class), 8] => File.read(fixtures_path(:re_opened_class)).lines.to_a[7..10].join(""),
|
192
|
+
[fixtures_path(:re_opened_class), 12] => File.read(fixtures_path(:re_opened_class)).lines.to_a[11..14].join("")
|
193
|
+
}
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe "for class with only class methods" do
|
198
|
+
before do
|
199
|
+
test_load 'ClassMethodsClass'
|
200
|
+
end
|
201
|
+
it "knows the unique class methods of the class" do
|
202
|
+
ClassMethodsClass.__source__.class_methods.unique.should == [:simple_method]
|
203
|
+
end
|
204
|
+
it "can find the source" do
|
205
|
+
ClassMethodsClass.__source__.should == File.read(fixtures_path(:class_methods_class)).lines.to_a[0..-1].join("")
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe "it doesn't cause side effects for class-scoped code" do
|
210
|
+
before do
|
211
|
+
test_load 'SideEffectClass'
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should retain the same values as set" do
|
215
|
+
SideEffectClass.should be_active
|
216
|
+
SideEffectClass.deactivate!
|
217
|
+
SideEffectClass.should_not be_active
|
218
|
+
SideEffectClass.__source__.should == File.read(fixtures_path(:side_effect_class)).lines.to_a[0..-1].join("")
|
219
|
+
SideEffectClass.should_not be_active
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class OuterClass
|
2
|
+
def simple_method_1
|
3
|
+
end
|
4
|
+
|
5
|
+
class OtherNestedClass
|
6
|
+
def example_method
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def simple_method_2
|
11
|
+
end
|
12
|
+
|
13
|
+
class NestedClass
|
14
|
+
def example_method
|
15
|
+
puts "example"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def simple_method_3
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
SOURCE_PATH=File.expand_path(File.dirname(__FILE__), '../lib')
|
3
|
+
$LOAD_PATH << SOURCE_PATH
|
4
|
+
require 'class_source'
|
5
|
+
|
6
|
+
RSpec.configure do
|
7
|
+
def spec_path
|
8
|
+
@path ||= File.expand_path(File.dirname(__FILE__))
|
9
|
+
end
|
10
|
+
|
11
|
+
def fixtures_path(file_name)
|
12
|
+
"#{SOURCE_PATH}/fixtures/#{file_name}.rb"
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_load(*args)
|
16
|
+
name = args.shift
|
17
|
+
file_name = name.to_s.gsub(/([A-Z])/) { |match| "_" + match.downcase }.slice(1..-1)
|
18
|
+
raise "can't find #{fixtures_path(file_name)}" unless File.exist?(fixtures_path(file_name))
|
19
|
+
|
20
|
+
@loaded_fixtures ||= []
|
21
|
+
@loaded_fixtures << name
|
22
|
+
existing_classes = Object.constants
|
23
|
+
load fixtures_path(file_name)
|
24
|
+
new_classes = Object.constants - existing_classes - [name.to_sym]
|
25
|
+
@loaded_fixtures += new_classes.map &:to_s
|
26
|
+
allow_source_inspection(name)
|
27
|
+
|
28
|
+
test_load(*args) unless args.empty?
|
29
|
+
new_classes.each { |new_name| allow_source_inspection(new_name) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def allow_source_inspection(name, nesting = Object)
|
33
|
+
klass = nesting.const_get(name.to_sym)
|
34
|
+
|
35
|
+
klass.send :extend, ClassSource
|
36
|
+
klass.constants.select { |kc| klass.const_get(kc).is_a?(Class) }.each do |nested_class|
|
37
|
+
allow_source_inspection nested_class, klass
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_unload_all
|
42
|
+
@loaded_fixtures.each do |klass_name|
|
43
|
+
Object.send :remove_const, klass_name if Object.const_defined?(klass_name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: class_source
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Austin Putman, austingfromboston
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-21 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &2152549200 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2152549200
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ruby_parser
|
27
|
+
requirement: &2152548780 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2152548780
|
36
|
+
description: Expose source files and full source code for each class via a simple
|
37
|
+
API
|
38
|
+
email:
|
39
|
+
- austin@rawfingertips.com
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- lib/class_source/collator.rb
|
45
|
+
- lib/class_source/declarations.rb
|
46
|
+
- lib/class_source/guesser.rb
|
47
|
+
- lib/class_source/index.rb
|
48
|
+
- lib/class_source/locator.rb
|
49
|
+
- lib/class_source/method_index.rb
|
50
|
+
- lib/class_source/version.rb
|
51
|
+
- lib/class_source.rb
|
52
|
+
- spec/class_source_spec.rb
|
53
|
+
- spec/fixtures/class_methods_class.rb
|
54
|
+
- spec/fixtures/cloned_class.rb
|
55
|
+
- spec/fixtures/duplicated_class.rb
|
56
|
+
- spec/fixtures/dynamic_class.rb
|
57
|
+
- spec/fixtures/eval_class.rb
|
58
|
+
- spec/fixtures/example_code.rb
|
59
|
+
- spec/fixtures/nested_example.rb
|
60
|
+
- spec/fixtures/no_methods_class.rb
|
61
|
+
- spec/fixtures/outer_class.rb
|
62
|
+
- spec/fixtures/protected_method_class.rb
|
63
|
+
- spec/fixtures/re_opened_class.rb
|
64
|
+
- spec/fixtures/re_opened_class_2.rb
|
65
|
+
- spec/fixtures/side_effect_class.rb
|
66
|
+
- spec/fixtures/simple_class.rb
|
67
|
+
- spec/fixtures/sub_class.rb
|
68
|
+
- spec/fixtures/unnested_child.rb
|
69
|
+
- spec/spec_helper.rb
|
70
|
+
homepage: http://github.com/austinfromboston/class_source
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project: class_source
|
91
|
+
rubygems_version: 1.8.6
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: See where your classes are defined
|
95
|
+
test_files:
|
96
|
+
- spec/class_source_spec.rb
|
97
|
+
- spec/fixtures/class_methods_class.rb
|
98
|
+
- spec/fixtures/cloned_class.rb
|
99
|
+
- spec/fixtures/duplicated_class.rb
|
100
|
+
- spec/fixtures/dynamic_class.rb
|
101
|
+
- spec/fixtures/eval_class.rb
|
102
|
+
- spec/fixtures/example_code.rb
|
103
|
+
- spec/fixtures/nested_example.rb
|
104
|
+
- spec/fixtures/no_methods_class.rb
|
105
|
+
- spec/fixtures/outer_class.rb
|
106
|
+
- spec/fixtures/protected_method_class.rb
|
107
|
+
- spec/fixtures/re_opened_class.rb
|
108
|
+
- spec/fixtures/re_opened_class_2.rb
|
109
|
+
- spec/fixtures/side_effect_class.rb
|
110
|
+
- spec/fixtures/simple_class.rb
|
111
|
+
- spec/fixtures/sub_class.rb
|
112
|
+
- spec/fixtures/unnested_child.rb
|
113
|
+
- spec/spec_helper.rb
|