robe-server 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/robe-server.svg)](https://badge.fury.io/rb/robe-server)
|
4
|
+
[![Build Status](https://travis-ci.org/AfsmNGhr/robe-server.svg?branch=master)](https://travis-ci.org/AfsmNGhr/robe-server "Build status from Travis CI")
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/github/AfsmNGhr/robe-server/badge.svg?branch=master)](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
|