class_source 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ module ClassSource
2
+ VERSION="0.0.0"
3
+ end
@@ -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,5 @@
1
+ class ClassMethodsClass
2
+ def self.simple_method
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class ClonedClass
2
+ def simple_method
3
+ end
4
+ end
5
+ CloneClass = ClonedClass.clone
@@ -0,0 +1,5 @@
1
+ class DuplicatedClass
2
+ def simple_method
3
+ end
4
+ end
5
+ DuplicateClass = DuplicatedClass.dup
@@ -0,0 +1,14 @@
1
+ DynamicClass = Class.new do
2
+ def example_method
3
+
4
+ end
5
+ end
6
+
7
+ class_to_be_named_later = Class.new do
8
+ def example_method
9
+ end
10
+ end
11
+
12
+ LateNamedDynamicClass = class_to_be_named_later
13
+ MethodlessDynamicClass = Class.new
14
+
@@ -0,0 +1,11 @@
1
+ method_name = "simple_method"
2
+ class_name = "EvalClass"
3
+
4
+ code = <<-CODE
5
+ class #{class_name}
6
+ def #{method_name}
7
+ end
8
+ end
9
+ CODE
10
+
11
+ eval code, binding, __FILE__, __LINE__
@@ -0,0 +1,10 @@
1
+ class Car
2
+ def initialize
3
+ @wheels = 3
4
+ end
5
+
6
+ def go
7
+ puts "Vroom vroom"
8
+ true
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class ParentClass
2
+ class ChildClass
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class NoMethodsClass
2
+ class NestedClass
3
+
4
+ end
5
+ 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
@@ -0,0 +1,19 @@
1
+ class ProtectedMethodClass
2
+ def initialize(foo)
3
+
4
+ end
5
+
6
+ def talk(blab)
7
+ sku.foo = doo
8
+ end
9
+
10
+ protected
11
+ def whisper(mutter)
12
+ foo.doo = sku
13
+ end
14
+
15
+ private
16
+ def think(mentate)
17
+ doo.sku = foo
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ load fixtures_path(:re_opened_class_2)
2
+
3
+ class ReOpenedClass
4
+ def method1
5
+
6
+ end
7
+
8
+ class NestedClass
9
+ def simple_method
10
+ end
11
+ end
12
+ class NestedClass
13
+ def simple_method_2
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ class ReOpenedClass
2
+ def method2
3
+
4
+ end
5
+
6
+ class NestedClass2
7
+ def simple_method
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class SideEffectClass
2
+ @@active = true
3
+
4
+ def self.deactivate!
5
+ @@active = false
6
+ end
7
+
8
+ def self.active?
9
+ @@active
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+
2
+
3
+ class SimpleClass
4
+ def initialize
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ class SubClass < ProtectedMethodClass
2
+ def talk
3
+ "hallo"
4
+ end
5
+
6
+ def think
7
+ "howzit"
8
+ end
9
+
10
+ end
@@ -0,0 +1,3 @@
1
+ class ChildClass
2
+
3
+ end
@@ -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