robe-server 1.0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +3 -0
- data/.travis.yml +19 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +85 -0
- data/LICENSE +674 -0
- data/README.md +21 -0
- data/Rakefile +10 -0
- data/lib/robe-server.rb +39 -0
- data/lib/robe/core_ext.rb +37 -0
- data/lib/robe/jvisor.rb +14 -0
- data/lib/robe/sash.rb +215 -0
- data/lib/robe/sash/doc_for.rb +57 -0
- data/lib/robe/sash/includes_tracker.rb +75 -0
- data/lib/robe/scanners.rb +49 -0
- data/lib/robe/server.rb +87 -0
- data/lib/robe/type_space.rb +57 -0
- data/lib/robe/visor.rb +56 -0
- data/robe-server.gemspec +19 -0
- data/spec/robe/jvisor_spec.rb +30 -0
- data/spec/robe/method_scanner_spec.rb +50 -0
- data/spec/robe/module_scanner_spec.rb +76 -0
- data/spec/robe/sash/doc_for_spec.rb +165 -0
- data/spec/robe/sash/includes_tracker_spec.rb +35 -0
- data/spec/robe/sash_spec.rb +420 -0
- data/spec/robe/server_spec.rb +64 -0
- data/spec/robe/type_space_spec.rb +126 -0
- data/spec/robe/visor_spec.rb +91 -0
- data/spec/robe_spec.rb +51 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/mocks.rb +31 -0
- metadata +116 -0
data/README.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
## Robe server
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/robe-server)
|
4
|
+
[](https://travis-ci.org/AfsmNGhr/robe-server "Build status from Travis CI")
|
5
|
+
[](https://coveralls.io/github/AfsmNGhr/robe-server?branch=master)
|
6
|
+
|
7
|
+
Server for [robe](https://github.com/dgutov/robe)
|
8
|
+
|
9
|
+
## Install
|
10
|
+
|
11
|
+
```.ruby
|
12
|
+
# Gemfile
|
13
|
+
|
14
|
+
gem 'robe-server'
|
15
|
+
```
|
16
|
+
|
17
|
+
or
|
18
|
+
|
19
|
+
```.bash
|
20
|
+
$ gem install robe-server
|
21
|
+
```
|
data/Rakefile
ADDED
data/lib/robe-server.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'robe/sash'
|
2
|
+
require 'robe/server'
|
3
|
+
|
4
|
+
module Robe
|
5
|
+
class << self
|
6
|
+
attr_accessor :server
|
7
|
+
|
8
|
+
def start(port = 0)
|
9
|
+
return running_string if @server
|
10
|
+
|
11
|
+
@server = Server.new(Sash.new, port)
|
12
|
+
|
13
|
+
['INT', 'TERM'].each do |signal|
|
14
|
+
trap(signal) { stop }
|
15
|
+
end
|
16
|
+
Thread.new do
|
17
|
+
unless Thread.current[:__yard_registry__]
|
18
|
+
Thread.current[:__yard_registry__] = Thread.main[:__yard_registry__]
|
19
|
+
end
|
20
|
+
@server.start
|
21
|
+
end
|
22
|
+
|
23
|
+
@server.wait_for_it
|
24
|
+
|
25
|
+
running_string
|
26
|
+
end
|
27
|
+
|
28
|
+
def stop
|
29
|
+
@server.shutdown
|
30
|
+
@server = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def running_string
|
36
|
+
"robe on #{@server.port}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Module
|
2
|
+
unless method_defined?(:__name__)
|
3
|
+
alias_method :__name__, :name
|
4
|
+
end
|
5
|
+
|
6
|
+
if method_defined?(:singleton_class?)
|
7
|
+
alias_method :__singleton_class__?, :singleton_class?
|
8
|
+
else
|
9
|
+
def __singleton_class__?
|
10
|
+
self != Class && ancestors.first != self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
unless method_defined?(:__singleton_class__)
|
15
|
+
alias_method :__singleton_class__, :singleton_class
|
16
|
+
end
|
17
|
+
|
18
|
+
unless method_defined?(:__include__?)
|
19
|
+
alias_method :__include__?, :include?
|
20
|
+
end
|
21
|
+
|
22
|
+
unless method_defined?(:__instance_methods__)
|
23
|
+
alias_method :__instance_methods__, :instance_methods
|
24
|
+
end
|
25
|
+
|
26
|
+
unless method_defined?(:__public_instance_methods__)
|
27
|
+
alias_method :__public_instance_methods__, :public_instance_methods
|
28
|
+
end
|
29
|
+
|
30
|
+
unless method_defined?(:__protected_instance_methods__)
|
31
|
+
alias_method :__protected_instance_methods__, :protected_instance_methods
|
32
|
+
end
|
33
|
+
|
34
|
+
unless method_defined?(:__private_instance_methods__)
|
35
|
+
alias_method :__private_instance_methods__, :private_instance_methods
|
36
|
+
end
|
37
|
+
end
|
data/lib/robe/jvisor.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'robe/visor'
|
2
|
+
|
3
|
+
module Robe
|
4
|
+
class JVisor < Visor
|
5
|
+
def each_object(mod)
|
6
|
+
# http://jira.codehaus.org/browse/JRUBY-7027
|
7
|
+
ObjectSpace.each_object(mod).select { |m| m.respond_to? :name }
|
8
|
+
end
|
9
|
+
|
10
|
+
def descendants(cls)
|
11
|
+
ObjectSpace.each_object(Class).select { |c| c < cls }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/robe/sash.rb
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'robe/sash/doc_for'
|
2
|
+
require 'robe/type_space'
|
3
|
+
require 'robe/scanners'
|
4
|
+
require 'robe/visor'
|
5
|
+
require 'robe/jvisor'
|
6
|
+
require 'robe/core_ext'
|
7
|
+
require 'robe/sash/includes_tracker'
|
8
|
+
|
9
|
+
module Robe
|
10
|
+
class Sash
|
11
|
+
attr_accessor :visor, :name_cache
|
12
|
+
|
13
|
+
def initialize(visor = pick_visor)
|
14
|
+
@visor = visor
|
15
|
+
init_name_cache
|
16
|
+
end
|
17
|
+
|
18
|
+
def class_locations(name, mod)
|
19
|
+
locations = {}
|
20
|
+
if (obj = visor.resolve_context(name, mod)) and obj.is_a? Module
|
21
|
+
methods = obj.methods(false).map { |m| obj.method(m) } +
|
22
|
+
obj.__instance_methods__(false).map { |m| obj.instance_method(m) }
|
23
|
+
methods.each do |m|
|
24
|
+
if loc = m.source_location
|
25
|
+
path = loc[0]
|
26
|
+
locations[path] ||= 0
|
27
|
+
locations[path] += 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
if defined? Class.class_attribute and Class != obj
|
32
|
+
locations.delete Class.method(:class_attribute).source_location[0]
|
33
|
+
end
|
34
|
+
locations.keys.sort { |k1, k2| -(locations[k1] <=> locations[k2]) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def modules
|
38
|
+
visor.each_object(Module).map { |mod| name_cache[mod] }.compact
|
39
|
+
end
|
40
|
+
|
41
|
+
def targets(obj)
|
42
|
+
obj = visor.resolve_const(obj)
|
43
|
+
if obj.is_a? Module
|
44
|
+
module_methods = obj.methods.map { |m| method_spec(obj.method(m)) }
|
45
|
+
instance_methods = (obj.__instance_methods__ +
|
46
|
+
obj.__private_instance_methods__(false))
|
47
|
+
.map { |m| method_spec(obj.instance_method(m)) }
|
48
|
+
[name_cache[obj]] + module_methods + instance_methods
|
49
|
+
else
|
50
|
+
self.targets(obj.class.to_s)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_method(mod, inst, sym)
|
55
|
+
mod.__send__(inst ? :instance_method : :method, sym)
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_method_owner(mod, inst, sym)
|
59
|
+
begin
|
60
|
+
find_method(mod, inst, sym).owner
|
61
|
+
rescue NameError
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_spec(method)
|
66
|
+
owner, inst = method.owner, nil
|
67
|
+
if owner.__singleton_class__?
|
68
|
+
name = owner.to_s[/Class:([A-Z][^\(> ]*)/, 1] # defined in an eigenclass
|
69
|
+
elsif name = name_cache[owner]
|
70
|
+
inst = true
|
71
|
+
elsif !owner.is_a?(Class)
|
72
|
+
name, inst = IncludesTracker.method_owner_and_inst(owner, name_cache)
|
73
|
+
end
|
74
|
+
# XXX: We can speed this up further by only returning the
|
75
|
+
# method's (or owner's) object_id here, and resolve the "real"
|
76
|
+
# host's name only when it's really needed. But that would break
|
77
|
+
# the current 'meta' impl in company-robe, for one thing.
|
78
|
+
[name, inst, method.name, method.parameters] + method.source_location.to_a
|
79
|
+
end
|
80
|
+
|
81
|
+
def doc_for(mod, inst, sym)
|
82
|
+
resolved = visor.resolve_const(mod)
|
83
|
+
DocFor.new(find_method(resolved, inst, sym.to_sym)).format
|
84
|
+
end
|
85
|
+
|
86
|
+
def method_targets(name, target, mod, instance, superc, conservative)
|
87
|
+
sym = name.to_sym
|
88
|
+
space = TypeSpace.new(visor, target, mod, instance, superc)
|
89
|
+
special_method = superc
|
90
|
+
|
91
|
+
scanner = ModuleScanner.new(sym, special_method || !target)
|
92
|
+
|
93
|
+
space.scan_with(scanner)
|
94
|
+
targets = scanner.candidates
|
95
|
+
|
96
|
+
if targets
|
97
|
+
targets.delete(Class.instance_method(:new))
|
98
|
+
filter_targets!(space, targets, instance, sym)
|
99
|
+
end
|
100
|
+
|
101
|
+
sc = space.target_type.singleton_class
|
102
|
+
|
103
|
+
if !instance && (sym == :new) && targets.all? { |t| t.owner < sc }
|
104
|
+
ctor_space = TypeSpace.new(visor, target, mod, true, superc)
|
105
|
+
ctor_scanner = ModuleScanner.new(:initialize, true)
|
106
|
+
ctor_space.scan_with(ctor_scanner)
|
107
|
+
ctor_targets = ctor_scanner.candidates
|
108
|
+
filter_targets!(ctor_space, ctor_targets, true, :initialize)
|
109
|
+
targets += ctor_targets
|
110
|
+
end
|
111
|
+
|
112
|
+
if targets.empty? && (target || !conservative) && !special_method
|
113
|
+
unless target
|
114
|
+
scanner.scan_methods(Kernel, :__private_instance_methods__)
|
115
|
+
end
|
116
|
+
scanner.check_private = false
|
117
|
+
scanner.scan(visor.each_object(Module), true, true)
|
118
|
+
targets = scanner.candidates
|
119
|
+
end
|
120
|
+
|
121
|
+
targets.map { |method| method_spec(method) }
|
122
|
+
.sort_by { |(mname)| mname ? mname.scan(/::/).length : 99 }
|
123
|
+
end
|
124
|
+
|
125
|
+
def filter_targets!(space, targets, instance, sym)
|
126
|
+
owner = find_method_owner(space.target_type, instance, sym)
|
127
|
+
if owner
|
128
|
+
targets.reject! do |method|
|
129
|
+
!(method.owner <= owner) &&
|
130
|
+
targets.find { |other| other.owner < method.owner }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def complete_method(prefix, target, mod, instance)
|
136
|
+
space = TypeSpace.new(visor, target, mod, instance, nil)
|
137
|
+
scanner = MethodScanner.new(prefix, !target)
|
138
|
+
|
139
|
+
space.scan_with(scanner)
|
140
|
+
|
141
|
+
if scanner.candidates.empty?
|
142
|
+
scanner.check_private = false
|
143
|
+
scanner.scan(visor.each_object(Module), true, true)
|
144
|
+
end
|
145
|
+
|
146
|
+
scanner.candidates.map { |m| method_spec(m) }
|
147
|
+
end
|
148
|
+
|
149
|
+
def complete_const(prefix, mod)
|
150
|
+
colons = prefix.rindex("::")
|
151
|
+
tail = colons ? prefix[colons + 2..-1] : prefix
|
152
|
+
if !colons
|
153
|
+
path = [Object]
|
154
|
+
path += visor.resolve_path(mod) if mod
|
155
|
+
path.flat_map do |m|
|
156
|
+
complete_const_in_module(tail, m)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
base_name = prefix[0..colons + 1]
|
160
|
+
base = unless colons == 0
|
161
|
+
if mod
|
162
|
+
visor.resolve_context(base_name[0..-3], mod)
|
163
|
+
else
|
164
|
+
visor.resolve_const(base_name)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
complete_const_in_module(tail, base || Object)
|
168
|
+
end.map { |c| "#{base_name}#{c}" }
|
169
|
+
end
|
170
|
+
|
171
|
+
def complete_const_in_module(tail, base)
|
172
|
+
base.constants.grep(/^#{Regexp.escape(tail)}/)
|
173
|
+
end
|
174
|
+
|
175
|
+
def rails_refresh
|
176
|
+
if defined?(Rails.application.reloader)
|
177
|
+
Rails.application.reloader.reload!
|
178
|
+
else
|
179
|
+
ActionDispatch::Reloader.cleanup!
|
180
|
+
ActionDispatch::Reloader.prepare!
|
181
|
+
end
|
182
|
+
Rails.application.eager_load!
|
183
|
+
init_name_cache
|
184
|
+
end
|
185
|
+
|
186
|
+
def load_path
|
187
|
+
$LOAD_PATH
|
188
|
+
end
|
189
|
+
|
190
|
+
def ping
|
191
|
+
"pong"
|
192
|
+
end
|
193
|
+
|
194
|
+
def call(path, body)
|
195
|
+
_, endpoint, *args = path.split("/").map { |s| s == "-" ? nil : s }
|
196
|
+
value = public_send(endpoint.to_sym, *args)
|
197
|
+
value.to_json
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def pick_visor
|
203
|
+
if RUBY_ENGINE == "jruby"
|
204
|
+
JVisor.new
|
205
|
+
else
|
206
|
+
Visor.new
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def init_name_cache
|
211
|
+
# https://www.ruby-forum.com/topic/167055
|
212
|
+
@name_cache = Hash.new { |h, mod| h[mod] = mod.__name__ }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'pry-doc' if RUBY_ENGINE == "ruby"
|
6
|
+
rescue LoadError
|
7
|
+
# Whatever, it's optional.
|
8
|
+
end
|
9
|
+
|
10
|
+
module Robe
|
11
|
+
class Sash
|
12
|
+
class DocFor
|
13
|
+
def initialize(method)
|
14
|
+
@method = method
|
15
|
+
end
|
16
|
+
|
17
|
+
def format
|
18
|
+
info = self.class.method_struct(@method)
|
19
|
+
{docstring: info.docstring,
|
20
|
+
source: info.source,
|
21
|
+
aliases: info.aliases,
|
22
|
+
visibility: visibility}
|
23
|
+
end
|
24
|
+
|
25
|
+
def visibility
|
26
|
+
owner, name = @method.owner, @method.name
|
27
|
+
if owner.__public_instance_methods__(false).include?(name)
|
28
|
+
:public
|
29
|
+
elsif owner.__protected_instance_methods__(false).include?(name)
|
30
|
+
:protected
|
31
|
+
elsif owner.__private_instance_methods__(false).include?(name)
|
32
|
+
:private
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.method_struct(method)
|
37
|
+
begin
|
38
|
+
info = Pry::Method.new(method)
|
39
|
+
|
40
|
+
if info.dynamically_defined?
|
41
|
+
doc = ""
|
42
|
+
source = "# This method was defined outside of a source file."
|
43
|
+
else
|
44
|
+
doc = info.doc
|
45
|
+
source = (info.source? ? info.source : "# Not available.")
|
46
|
+
end
|
47
|
+
|
48
|
+
OpenStruct.new(docstring: doc, source: source,
|
49
|
+
aliases: info.aliases.map(&:to_sym))
|
50
|
+
rescue Pry::CommandError
|
51
|
+
message = $!.message =~ /pry-doc/ ? $!.message : ""
|
52
|
+
return OpenStruct.new(docstring: message)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'robe/core_ext'
|
2
|
+
|
3
|
+
module Robe
|
4
|
+
class Sash
|
5
|
+
class IncludesTracker
|
6
|
+
def self.method_owner_and_inst(owner, name_cache)
|
7
|
+
includers = maybe_scan
|
8
|
+
|
9
|
+
mod, inst = includers[owner].first
|
10
|
+
|
11
|
+
if mod
|
12
|
+
[name_cache[mod], inst]
|
13
|
+
else
|
14
|
+
[nil, true]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.reset!
|
19
|
+
@@hosts = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def self.maybe_scan
|
25
|
+
includers = @@hosts
|
26
|
+
|
27
|
+
unless includers
|
28
|
+
@@hosts = includers = Hash.new { |h, k| h[k] = [] }
|
29
|
+
|
30
|
+
ObjectSpace.each_object(Module) do |cl|
|
31
|
+
next unless cl.respond_to?(:included_modules)
|
32
|
+
next if cl.__singleton_class__?
|
33
|
+
cl.included_modules.each { |mod| includers[mod] << [cl, true] }
|
34
|
+
sc = cl.__singleton_class__
|
35
|
+
sc.included_modules.each { |mod| includers[mod] << [cl, nil] }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
includers
|
40
|
+
end
|
41
|
+
|
42
|
+
if Module.respond_to?(:prepend)
|
43
|
+
module Invalidator
|
44
|
+
def included(other)
|
45
|
+
IncludesTracker.reset!
|
46
|
+
super(other)
|
47
|
+
end
|
48
|
+
|
49
|
+
def extended(other)
|
50
|
+
IncludesTracker.reset!
|
51
|
+
super(other)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Module.send(:prepend, Invalidator)
|
56
|
+
else
|
57
|
+
Module.class_eval do
|
58
|
+
alias_method :__orig_included, :included
|
59
|
+
alias_method :__orig_extended, :extended
|
60
|
+
|
61
|
+
# Cannot hook into this method without :prepend.
|
62
|
+
def included(other)
|
63
|
+
IncludesTracker.reset!
|
64
|
+
__orig_included(other)
|
65
|
+
end
|
66
|
+
|
67
|
+
def extended(other)
|
68
|
+
IncludesTracker.reset!
|
69
|
+
__orig_extended(other)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|