reflexive 0.0.6 → 0.1.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/Gemfile CHANGED
@@ -2,15 +2,17 @@ source "http://rubygems.org"
2
2
 
3
3
  # keeping this in sync with reflexive.gemspec manually
4
4
 
5
- gem "rack"
6
- gem "sinatra"
7
- gem "sinatra_more"
8
- gem "coderay"
9
- gem "rdiscount"
5
+ gem "rack", "1.1.0"
6
+ gem "sinatra", "1.0"
7
+ gem "sinatra_more", "0.3.40"
8
+ gem "coderay", "0.9.3"
9
+ # gem "rdiscount"
10
10
 
11
11
  group :development do
12
12
  gem "rails", "3.0.0.beta3"
13
13
  gem "rspec", "2.0.0.beta.8"
14
- gem "sinatra-reloader"
15
- gem "thin"
14
+ gem "sinatra-reloader", "0.4.1"
15
+ gem "thin", "1.2.7"
16
+ gem "rack-test", "0.5.3"
17
+ gem "webrat", "0.7.1"
16
18
  end
data/Rakefile CHANGED
@@ -8,14 +8,20 @@ end
8
8
  GEM_NAME = "reflexive"
9
9
 
10
10
  desc "Relese next version of reflexive gem (do that just after `git commit')"
11
- task :release do
11
+ task :release => :spec do
12
12
  require "rubygems"
13
13
  require "rubygems/version"
14
14
  require "yaml"
15
15
 
16
- current_version = YAML.load(`gem specification #{ GEM_NAME } -r`)["version"] || Gem::Version.new("0.0.0")
17
- new_version = (current_version.segments[0..-2] + [current_version.segments[-1].succ]).join(".")
18
- ENV["GEM_VERSION"] = new_version
16
+ if ENV["GEM_VERSION"]
17
+ # release version passed in ENV["GEM_VERSION"]
18
+ new_version = ENV["GEM_VERSION"]
19
+ else
20
+ # by default release next minor version
21
+ current_version = YAML.load(`gem specification #{ GEM_NAME } -r`)["version"] || Gem::Version.new("0.0.0")
22
+ new_version = (current_version.segments[0..-2] + [current_version.segments[-1].succ]).join(".")
23
+ ENV["GEM_VERSION"] = new_version
24
+ end
19
25
 
20
26
  puts "Releasing #{ GEM_NAME } #{ new_version }"
21
27
 
data/config.ru CHANGED
@@ -1,3 +1,3 @@
1
- require File.expand_path("../lib/reflexive/application", __FILE__)
1
+ require ::File.expand_path("../lib/reflexive/application", __FILE__)
2
2
 
3
3
  run Reflexive::Application
@@ -12,10 +12,12 @@ require "reflexive/columnizer"
12
12
  require "reflexive/constantize"
13
13
  require "reflexive/descendants"
14
14
  require "reflexive/methods"
15
+ require "reflexive/method_lookup"
15
16
 
16
17
  if ENV["SINATRA_RELOADER"]
17
18
  require "rails/all"
18
19
  require "arel"
20
+ require File.expand_path("../../../spec/integration_spec_fixture", __FILE__)
19
21
 
20
22
  module ::Kernel
21
23
  def r(*args)
@@ -60,10 +62,11 @@ module Reflexive
60
62
  end
61
63
 
62
64
  action "constant_lookup" do
63
- if klass = Reflexive.constant_lookup(*params.values_at(:name, :scope))
65
+ if (klass = Reflexive.constant_lookup(*params.values_at(:name, :scope))) &&
66
+ (klass.instance_of?(Class) || klass.instance_of?(Module))
64
67
  redirect(constant_path(klass.to_s))
65
68
  else
66
- e "failed to lookup constant `#{ params[:name] }' in scope #{ params[:scope] }"
69
+ e "failed to lookup class/module with name `#{ params[:name] }' in scope #{ params[:scope] }"
67
70
  end
68
71
  end
69
72
 
@@ -87,54 +90,71 @@ module Reflexive
87
90
  end
88
91
  end
89
92
 
90
- get %r</reflexive/constants/([^/&#]+)/class_methods/([^/&#]+)/definition> do |klass, method|
93
+ def definition_action(klass, level, name)
91
94
  find_klass(klass)
92
- @method_name = method
93
- @path, @line = @klass.method(@method_name).source_location
94
- @source = highlight_file(@path, :highlight_lines => [@line])
95
- erb :methods_definition
95
+ @method_name = name
96
+ @path, @line = @klass.send(level == :class ? :method : :instance_method, @method_name).source_location
97
+ if @path.include?("(eval)")
98
+ e "#{ name } #{ level } method was generated using `eval' function and can't be browsed"
99
+ else
100
+ @source = highlight_file(@path, :highlight_lines => [@line])
101
+ erb :methods_definition
102
+ end
103
+ end
104
+
105
+ get %r</reflexive/constants/([^/&#]+)/class_methods/([^/&#]+)/definition> do |klass, method|
106
+ definition_action(klass, :class, method)
96
107
  end
97
108
 
98
109
  get %r</reflexive/constants/([^/&#]+)/instance_methods/([^/&#]+)/definition> do |klass, method|
110
+ definition_action(klass, :instance, method)
111
+ end
112
+
113
+ get %r</reflexive/constants/([^/&#]+)/instance_methods/([^/&#]+)/apidock> do |klass, method|
99
114
  find_klass(klass)
100
115
  @method_name = method
101
- @path, @line = @klass.instance_method(@method_name).source_location
102
- @source = highlight_file(@path, :highlight_lines => [@line])
103
- erb :methods_definition
116
+ @level = :instance
117
+ erb :methods_apidock
104
118
  end
105
119
 
106
- get %r</reflexive/constants/([^/&#]+)/methods/([^/&#]+)/apidock> do |klass, method|
120
+ get %r</reflexive/constants/([^/&#]+)/class_methods/([^/&#]+)/apidock> do |klass, method|
107
121
  find_klass(klass)
108
122
  @method_name = method
123
+ @level = :class
109
124
  erb :methods_apidock
110
125
  end
111
126
 
112
- get %r</reflexive/constants/([^/&#]+)/class_methods/([^/&#]+)> do |klass, method|
113
- find_klass(klass)
114
- begin
115
- if @klass.method(method).source_location
116
- redirect(class_method_definition_path(klass, method) +
117
- "#highlighted")
127
+ def method_lookup_action(klass, level, name)
128
+ lookup = MethodLookup.new(klass: klass, level: level, name: name)
129
+ if definitions = lookup.definitions
130
+ if definitions.size == 1
131
+ redirect(new_method_definition_path(*definitions[0]) + "#highlighted")
132
+ else
133
+ @definitions, @klass, @level, @name, @last_resort_lookup_used =
134
+ definitions, klass, level, name, lookup.last_resort_lookup_used?
135
+ erb :methods_choose
136
+ end
137
+ elsif documentations = lookup.documentations
138
+ if documentations.size == 1
139
+ redirect(method_documentation_path(*documentations[0]))
118
140
  else
119
- redirect(method_documentation_path(klass, method))
141
+ raise ArgumentError, "don't know how to handle multiple documentations"
120
142
  end
121
- rescue NameError
122
- e "failed to find `#{ method }' class method for #{ klass }"
143
+ else
144
+ e "failed to find `#{ name }' #{ level } method for #{ klass }"
123
145
  end
146
+ #
147
+ # e "failed to find `#{ method }' instance method for #{ klass }"
148
+ end
149
+
150
+ get %r</reflexive/constants/([^/&#]+)/class_methods/([^/&#]+)> do |klass, method|
151
+ find_klass(klass)
152
+ method_lookup_action(@klass, :class, method)
124
153
  end
125
154
 
126
155
  get %r</reflexive/constants/([^/&#]+)/instance_methods/([^/&#]+)> do |klass, method|
127
156
  find_klass(klass)
128
- begin
129
- if @klass.instance_method(method).source_location
130
- redirect(instance_method_definition_path(klass, method) +
131
- "#highlighted")
132
- else
133
- redirect(method_documentation_path(klass, method))
134
- end
135
- rescue NameError
136
- e "failed to find `#{ method }' instance method for #{ klass }"
137
- end
157
+ method_lookup_action(@klass, :instance, method)
138
158
  end
139
159
 
140
160
  get %r</reflexive/constants/([^/&#]+)> do |klass|
@@ -33,7 +33,7 @@ module Reflexive
33
33
  super(text, type)
34
34
  @out << "</a>"
35
35
  else
36
- super(text, type) rescue raise([text, type].inspect)
36
+ super(text, type)
37
37
  end
38
38
  end
39
39
 
@@ -49,6 +49,7 @@ module Reflexive
49
49
  in_backtick = false
50
50
  in_symbol = false
51
51
  in_embexpr_nesting = 0
52
+
52
53
  scanner_events.each do |scanner_event|
53
54
  token_val, event, tags =
54
55
  ReflexiveRipper.destruct_scanner_event(scanner_event)
@@ -107,6 +108,7 @@ module Reflexive
107
108
  # }
108
109
  # ;
109
110
  @coderay_tokens << [token_val, :content]
111
+ @coderay_tokens.last << tags if tags
110
112
  @coderay_tokens << [:close, :symbol]
111
113
  in_symbol = false
112
114
  elsif ripper_token == :regexp_beg
@@ -0,0 +1,44 @@
1
+ class Module
2
+ if RUBY_VERSION > '1.9.1'
3
+ alias reflexive_public_instance_methods public_instance_methods
4
+ alias reflexive_private_instance_methods private_instance_methods
5
+ alias reflexive_protected_instance_methods protected_instance_methods
6
+ else
7
+ # Try to workaround 1.9.1 *_instance_methods issues
8
+ def reflexive_public_instance_methods(inc_super = true)
9
+ if inc_super
10
+ public_instance_methods(true)
11
+ else
12
+ methods = public_instance_methods(false)
13
+ methods -= Module.public_instance_methods(false) unless self === Module
14
+ methods -= Class.public_instance_methods(false) unless self === Class
15
+ # ancestors.each do |ancestor|
16
+ # methods -= ancestor.public_instance_methods(false)
17
+ # end
18
+ methods
19
+ end
20
+ end
21
+
22
+ def reflexive_protected_instance_methods(inc_super = true)
23
+ if inc_super
24
+ protected_instance_methods(true)
25
+ else
26
+ methods = protected_instance_methods(false)
27
+ methods -= Module.protected_instance_methods(false) unless self === Module
28
+ methods -= Class.protected_instance_methods(false) unless self === Class
29
+ methods
30
+ end
31
+ end
32
+
33
+ def reflexive_private_instance_methods(inc_super = true)
34
+ if inc_super
35
+ private_instance_methods(true)
36
+ else
37
+ methods = private_instance_methods(false)
38
+ methods -= Module.private_instance_methods(false) unless self === Module
39
+ methods -= Class.private_instance_methods(false) unless self === Class
40
+ methods
41
+ end
42
+ end
43
+ end
44
+ end
@@ -34,8 +34,18 @@ module Reflexive
34
34
  ancestors = c.singleton_class.ancestors[0 .. (generations || -1)]
35
35
  subclass << c if ancestors.include?(klass)
36
36
  end
37
-
38
- subclass
37
+
38
+ if klass.instance_of?(Module)
39
+ # descendants of module are also modules which include the module
40
+ ObjectSpace.each_object(Module) do |c|
41
+ next if c == klass
42
+
43
+ ancestors = c.ancestors[0 .. (generations || -1)]
44
+ subclass << c if ancestors.include?(klass)
45
+ end
46
+ end
47
+
48
+ subclass.uniq
39
49
  end
40
50
  end
41
51
 
@@ -3,15 +3,26 @@ require "reflexive/coderay_ruby_scanner"
3
3
  require "reflexive/coderay_html_encoder"
4
4
 
5
5
  module Reflexive
6
+ def self.class_reloading_active?
7
+ if defined?(Rails)
8
+ !Rails.configuration.cache_classes rescue false
9
+ else
10
+ false
11
+ end
12
+ end
13
+
6
14
  FILE_EXT = /\.\w+\z/
7
15
  DL_EXT = /\.s?o\z/
8
16
  def self.load_path_lookup(path)
9
17
  path_with_rb_ext = path.sub(FILE_EXT, "") + ".rb"
10
- feature = nil
11
- $LOAD_PATH.detect do |load_path|
12
- File.exists?(feature = File.join(load_path, path_with_rb_ext))
18
+
19
+ $LOAD_PATH.each do |load_path|
20
+ if feature = File.join(load_path, path_with_rb_ext)
21
+ return feature if File.exists?(feature)
22
+ end
13
23
  end
14
- feature
24
+
25
+ nil
15
26
  end
16
27
 
17
28
  def self.loaded_features_lookup(path)
@@ -48,11 +59,11 @@ module Reflexive
48
59
  tokens = CodeRayRubyScanner.new(src).tokenize
49
60
  encoder = CodeRayHtmlEncoder.new(options)
50
61
  encoder.encode_tokens(tokens)
51
- elsif path =~ /\.(markdown|md)$/
52
- require "rdiscount"
53
- src = IO.read(path)
54
- markdown = RDiscount.new(src)
55
- markdown.to_html
62
+ # elsif path =~ /\.(markdown|md)$/
63
+ # require "rdiscount"
64
+ # src = IO.read(path)
65
+ # markdown = RDiscount.new(src)
66
+ # markdown.to_html
56
67
  else
57
68
  CodeRay.scan_file(path).html(options).div
58
69
  end
@@ -169,6 +180,12 @@ module Reflexive
169
180
  :title => (method_name if link_text.include?("...")))
170
181
  end
171
182
 
183
+ def full_name_link_to_method(constant, level, method_name)
184
+ link_text = "#{ constant }#{ level == :instance ? "#" : "." }#{ method_name }"
185
+ link_to(Rack::Utils.escape_html(link_text),
186
+ new_method_path(constant, level, method_name))
187
+ end
188
+
172
189
  ##
173
190
  # Truncates a given text after a given :length if text is longer than :length (defaults to 30).
174
191
  # The last characters will be replaced with the :omission (defaults to "в_│") for a total length not exceeding :length.
@@ -0,0 +1,199 @@
1
+ require "reflexive/constantize"
2
+
3
+ module Reflexive
4
+ class MethodLookup
5
+ def initialize(options)
6
+ unless (@klass, @level, @name = options.values_at(:klass, :level, :name)).all?
7
+ raise ArgumentError, "must pass :klass, :level, :name as named arguments"
8
+ end
9
+ @level, @name = @level.to_sym, @name.to_sym
10
+ end
11
+
12
+ def definitions
13
+ lookup unless @lookup_done
14
+ @definitions
15
+ end
16
+
17
+ def documentations
18
+ lookup unless @lookup_done
19
+ @documentations
20
+ end
21
+
22
+ def last_resort_lookup_used?
23
+ @last_resort_lookup_used
24
+ end
25
+
26
+ private
27
+
28
+ def lookup
29
+ begin
30
+ defined_method_lookup
31
+ rescue NameError => e
32
+ # don't swallow NameError if it's not related to the method we're looking for
33
+ raise unless e.message.include?(@name.to_s)
34
+ heuristic_lookup
35
+ last_resort_lookup unless lookup_succeed?
36
+ end
37
+ @lookup_done = true
38
+ end
39
+
40
+ def lookup_succeed?
41
+ @definitions && @definitions.size > 0 ||
42
+ @documentations && @documentations.size > 0
43
+ end
44
+
45
+ def defined_method_lookup
46
+ unbound_method = @klass.send(method_getter, @name)
47
+ if unbound_method.source_location
48
+ @definitions = [[@klass, @level, @name]]
49
+ elsif @klass.instance_of?(Class) && @level == :class && @name == :new &&
50
+ (@klass.instance_method(:initialize).source_location rescue false)
51
+ @definitions = [[@klass, :instance, :initialize]]
52
+ elsif core_klass = unbound_method.owner
53
+ if core_klass == Kernel
54
+ @documentations = [[Kernel, :instance, @name]]
55
+ elsif core_klass == Module || core_klass.to_s == "#<Class:Module>"
56
+ if (Module.methods(false) + Module.private_methods(false)).include?(@name)
57
+ @documentations = [[Module, :class, @name]]
58
+ else
59
+ @documentations = [[Module, :instance, @name]]
60
+ end
61
+ elsif core_klass == Class
62
+ if (Class.methods(false) + Class.private_methods(false)).include?(@name)
63
+ @documentations = [[Class, :class, @name]]
64
+ else
65
+ @documentations = [[Class, :instance, @name]]
66
+ end
67
+ else
68
+ if @level == :class
69
+ if core_klass == @klass
70
+ @documentations = [[core_klass, :class, @name]]
71
+ elsif core_klass.to_s =~ /^#<Class:(.+)>$/
72
+ # get class from singleton class
73
+ @documentations = [[Reflexive.constantize($1), :class, @name]]
74
+ end
75
+ else
76
+ @documentations = [[core_klass, @level, @name]]
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def heuristic_lookup
83
+ if @klass.instance_of?(Module) && @level == :instance
84
+ # only instance methods are inherited from modules
85
+ potential_receivers = included_by_classes.select do |included_by_class|
86
+ all_instance_methods(included_by_class).include?(@name)
87
+ end
88
+
89
+ potential_receivers += included_by_modules.select do |included_by_module|
90
+ all_instance_methods(included_by_module).include?(@name)
91
+ end
92
+
93
+ potential_receivers.uniq.each do |receiver|
94
+ # TODO heuristic lookup shouldn't assume that found method is not core method
95
+ (@definitions ||= []) << [receiver, :instance, @name]
96
+ end
97
+
98
+ potential_class_receivers = included_by_singleton_classes.select do |included_by_singleton_class|
99
+ all_class_methods(included_by_singleton_class).include?(@name)
100
+ end
101
+
102
+ potential_class_receivers.uniq.each do |class_receiver|
103
+ (@definitions ||= []) << [class_receiver, :class, @name]
104
+ end
105
+ elsif @klass.instance_of?(Class)
106
+ # both instance and class methods are inherited by classes
107
+
108
+ potential_receivers = inherited_by_classes.select do |inherited_by_class|
109
+ all_level_methods(inherited_by_class).include?(@name)
110
+ end
111
+
112
+ potential_receivers.uniq.each do |receiver|
113
+
114
+ (@definitions ||= []) << [receiver, @level, @name]
115
+ end
116
+ end
117
+ end
118
+
119
+ def last_resort_lookup
120
+ @last_resort_lookup_used = true
121
+ seen = []
122
+ [Module, Class].each do |module_or_class|
123
+ ObjectSpace.each_object(module_or_class) do |m|
124
+ next if seen.include?(m)
125
+ if all_instance_methods(m).include?(@name)
126
+ (@definitions ||= []) << [m, :instance, @name]
127
+ end
128
+ if all_class_methods(m).include?(@name)
129
+ (@definitions ||= []) << [m, :class, @name]
130
+ end
131
+ seen << m
132
+ end
133
+ end
134
+ end
135
+
136
+ def all_instance_methods(klass)
137
+ klass.instance_methods(false) + klass.private_instance_methods(false)
138
+ end
139
+
140
+ def all_class_methods(klass)
141
+ klass.methods(false) + klass.private_methods(false)
142
+ end
143
+
144
+ def all_level_methods(klass)
145
+ @level == :instance ? all_instance_methods(klass) : all_class_methods(klass)
146
+ end
147
+
148
+ def included_by_classes
149
+ included_by = []
150
+ ObjectSpace.each_object(Class) do |c|
151
+ next if c == @klass
152
+
153
+ ancestors = c.ancestors
154
+ included_by << c if ancestors.include?(@klass)
155
+ end
156
+ included_by
157
+ end
158
+
159
+ alias inherited_by_classes included_by_classes
160
+
161
+ def included_by_modules
162
+ included_by = []
163
+ ObjectSpace.each_object(Module) do |m|
164
+ next if m == @klass
165
+
166
+ ancestors = m.ancestors
167
+ included_by << m if ancestors.include?(@klass)
168
+ end
169
+ included_by
170
+ end
171
+
172
+ def included_by_singleton_classes
173
+ included_by = []
174
+ ObjectSpace.each_object(Class) do |c|
175
+ next if c == @klass
176
+
177
+ ancestors = c.singleton_class.ancestors
178
+ included_by << c if ancestors.include?(@klass)
179
+ end
180
+ included_by
181
+ end
182
+
183
+ def method_getter
184
+ @level == :instance ? :instance_method : :method
185
+ end
186
+
187
+ def methods_getter
188
+ @level == :instance ? :instance_methods : :methods
189
+ end
190
+
191
+ def reverse_methods_getter
192
+ @level == :instance ? :methods : :instance_methods
193
+ end
194
+
195
+ def reverse_level
196
+ @level == :instance ? :class : :instance
197
+ end
198
+ end
199
+ end