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.
@@ -0,0 +1,49 @@
1
+ require 'robe/core_ext'
2
+
3
+ module Robe
4
+ class Scanner
5
+ attr_accessor :check_private
6
+ attr_reader :candidates
7
+
8
+ def initialize(sym, check_private)
9
+ @candidates = []
10
+ @sym = sym
11
+ @check_private = check_private
12
+ end
13
+
14
+ def scan(modules, check_instance, check_module)
15
+ modules.each do |m|
16
+ if check_module
17
+ sc = m.__singleton_class__
18
+ scan_methods(sc, :__instance_methods__)
19
+ scan_methods(sc, :__private_instance_methods__) if check_private
20
+ end
21
+ if check_instance
22
+ scan_methods(m, :__instance_methods__)
23
+ scan_methods(m, :__private_instance_methods__) if check_private
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ class ModuleScanner < Scanner
30
+ def scan_methods(mod, coll)
31
+ if mod.__send__(coll, false).include?(@sym)
32
+ candidates << mod.instance_method(@sym)
33
+ end
34
+ end
35
+ end
36
+
37
+ class MethodScanner < Scanner
38
+ def initialize(*args)
39
+ super
40
+ @re = /^#{Regexp.escape(@sym || "")}/
41
+ end
42
+
43
+ def scan_methods(mod, coll)
44
+ mod.__send__(coll, false).grep(@re) do |sym|
45
+ candidates << mod.instance_method(sym)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,87 @@
1
+ require 'json'
2
+ require 'tmpdir'
3
+ require 'socket'
4
+ require 'webrick'
5
+ require 'logger'
6
+
7
+ module Robe
8
+ class Server
9
+ attr_reader :running, :port
10
+
11
+ def initialize(handler, port)
12
+ @handler = handler
13
+ @server = TCPServer.new("127.0.0.1", port)
14
+ @running = true
15
+ @port = @server.addr[1]
16
+ end
17
+
18
+ def start
19
+ access = File.open("#{Dir.tmpdir}/robe-access-#{@port}.log", "w")
20
+ access.sync = true
21
+
22
+ error_logger = Logger.new($stderr)
23
+ access_logger = Logger.new(access)
24
+
25
+ client = nil
26
+
27
+ loop do
28
+ begin
29
+ client = @server.accept
30
+
31
+ next if client.eof?
32
+
33
+ req = WEBrick::HTTPRequest.new(:InputBufferSize => 1024,
34
+ :Logger => error_logger)
35
+ req.parse(client)
36
+ access_logger.info "#{req.request_method} #{req.path}"
37
+
38
+ begin
39
+ body = @handler.call(req.path, req.body)
40
+ rescue Exception => e
41
+ error_logger.error "Request failed: #{req.path}. Please file an issue."
42
+ error_logger.error "#{e.message}\n#{e.backtrace.join("\n")}"
43
+ end
44
+
45
+ resp = WEBrick::HTTPResponse.new(:OutputBufferSize => 1024,
46
+ :Logger => error_logger,
47
+ :HTTPVersion => "1.1")
48
+ resp.status = 200
49
+ resp.content_type = "application/json; charset=utf-8"
50
+ resp.body = body
51
+
52
+ begin
53
+ resp.send_response(client)
54
+ client.close
55
+ rescue Errno::EPIPE
56
+ error_logger.error "Connection lost, unsent response:"
57
+ error_logger.error body
58
+ end
59
+ rescue Errno::EINVAL
60
+ break
61
+ rescue IOError
62
+ # Hello JRuby
63
+ break
64
+ end
65
+ end
66
+ end
67
+
68
+ def wait_for_it
69
+ begin
70
+ TCPSocket.new("127.0.0.1", @port).close
71
+ rescue
72
+ sleep 0.05
73
+ retry
74
+ end
75
+ end
76
+
77
+ def shutdown
78
+ @running = false
79
+ begin
80
+ @server && @server.shutdown(Socket::SHUT_RDWR)
81
+ rescue Errno::ENOTCONN
82
+ # Hello JRuby
83
+ @server.close
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,57 @@
1
+ require 'robe/sash'
2
+
3
+ module Robe
4
+ class TypeSpace
5
+ attr_reader :visor, :target_type, :instance
6
+
7
+ def initialize(visor, target, mod, instance, superc)
8
+ @visor = visor
9
+ @instance = instance
10
+ @superc = superc
11
+ guess_target_type(target, mod)
12
+ end
13
+
14
+ def scan_with(scanner)
15
+ return unless obj = target_type
16
+ modules = obj.ancestors
17
+ modules -= obj.included_modules unless instance
18
+
19
+ if @superc
20
+ modules -= [obj]
21
+ else
22
+ modules += visor.descendants(obj).to_a
23
+ end
24
+
25
+ modules.push(Kernel) if instance && !obj.is_a?(Class)
26
+
27
+ if instance
28
+ if defined? ActiveSupport::Concern and obj.is_a?(ActiveSupport::Concern)
29
+ deps = obj.instance_variable_get("@_dependencies")
30
+ modules += deps if deps
31
+ end
32
+ end
33
+
34
+ scanner.scan(modules, instance, !instance)
35
+
36
+ unless instance
37
+ singleton_ancestors = obj.singleton_class.ancestors
38
+
39
+ if RUBY_VERSION >= "2.1.0"
40
+ # Ruby 2.1 includes all singletons in the ancestors chain
41
+ singleton_ancestors.reject!(&:__singleton_class__?)
42
+ end
43
+
44
+ scanner.scan(singleton_ancestors, true, false)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def guess_target_type(target, mod)
51
+ @target_type = visor.resolve_context(target, mod)
52
+ if @target_type && !(@target_type.is_a? Module)
53
+ @target_type, @instance = @target_type.class, true
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,56 @@
1
+ require 'forwardable'
2
+
3
+ module Robe
4
+ class Visor
5
+ extend Forwardable
6
+
7
+ def each_object(*args)
8
+ ObjectSpace.each_object(*args).reject { |m| m.__singleton_class__? }
9
+ end
10
+
11
+ def descendants(cls)
12
+ each_object(cls.singleton_class).to_a - [cls]
13
+ end
14
+
15
+ def resolve_context(name, mod)
16
+ return resolve_const(mod) unless name
17
+ unless name =~ /\A::/
18
+ nesting = mod ? mod.split("::") : []
19
+ resolve_path_elems(nesting).reverse.each do |elem|
20
+ begin
21
+ return elem.const_get(name)
22
+ rescue NameError
23
+ end
24
+ end
25
+ end
26
+ resolve_const(name)
27
+ end
28
+
29
+ def resolve_const(name)
30
+ resolve_path(name).last
31
+ end
32
+
33
+ def resolve_path(name)
34
+ return [] unless name
35
+ return [ARGF.class] if name == "ARGF.class"
36
+ if %w(IO::readable IO::writable).include?(name)
37
+ return [StringIO.included_modules.find { |m| m.name == name }]
38
+ end
39
+ nesting = name.split("::")
40
+ nesting.shift if nesting[0].empty?
41
+ resolve_path_elems(nesting)
42
+ end
43
+
44
+ def resolve_path_elems(nesting, init = Object)
45
+ c = init; ary = []
46
+ begin
47
+ nesting.each do |name|
48
+ ary << (c = c.const_get(name))
49
+ end
50
+ ary
51
+ rescue NameError
52
+ []
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = 'robe-server'
3
+ gem.version = '1.0.2'
4
+ gem.authors = 'Phil Hagelberg, Dmitry Gutov, Alexsey Ermolaev'
5
+ gem.email = 'afay.zangetsu@gmail.com'
6
+ gem.homepage = 'https://github.com/AfsmNGhr/robe-server'
7
+ gem.description = 'Server for robe'
8
+ gem.summary = 'Code navigation, documentation lookup and completion for Ruby'
9
+ gem.license = 'GPL-3.0'
10
+
11
+ gem.add_development_dependency 'rake', '~> 10.3'
12
+ gem.add_development_dependency 'rspec', '~> 2.14'
13
+
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.require_paths = ['lib']
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+
18
+ gem.extra_rdoc_files = ['LICENSE', 'README.md']
19
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require 'robe/jvisor'
3
+
4
+ describe Robe::JVisor do
5
+ subject { Robe::JVisor.new }
6
+
7
+ it "iterates though all native classes" do
8
+ res = subject.each_object(Module)
9
+ expect(res).to include(String)
10
+ expect(res).to include(Set)
11
+ expect(res).to include(Enumerable)
12
+ end
13
+
14
+ it "returns only modules responding to :name and :instance_methods" do
15
+ res = subject.each_object(Module)
16
+ expect(res.all? { |m| m.respond_to? :name }).to be_true
17
+ expect(res.all? { |m| m.respond_to? :instance_methods }).to be_true
18
+ end
19
+
20
+ it "returns descendants of a module" do
21
+ res = subject.descendants(Enumerable)
22
+ expect(res).to include(Array)
23
+ expect(res).not_to include(String)
24
+ end
25
+
26
+ it "returns descendants of a class" do
27
+ res = subject.descendants(Integer)
28
+ expect(res).to match_array([Bignum, Fixnum])
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'robe/scanners'
3
+
4
+ describe Robe::MethodScanner do
5
+ include ScannerHelper
6
+
7
+ klass = described_class
8
+
9
+ let(:a) { new_module(:f, :b, :b, :t) }
10
+ let(:b) { new_module(:bar, :foo, :baz, :tee) }
11
+ let(:c) { new_module(:foo, :bar, :baz, :tee) }
12
+ let(:d) { new_module(:foo, :bar, :tee, :baz) }
13
+ let(:modules) { [a, b, c, d] }
14
+
15
+ it "completes instance methods" do
16
+ scanner = klass.new(:f, false)
17
+ scanner.scan(modules, true, false)
18
+ expect(scanner.candidates).to have_names(:f, :foo, :foo)
19
+ end
20
+
21
+ it "completes module methods" do
22
+ scanner = klass.new(:b, false)
23
+ scanner.scan(modules, false, true)
24
+ expect(scanner.candidates).to have_names(:b, :bar, :bar)
25
+ end
26
+
27
+ it "completes private instance methods" do
28
+ scanner = klass.new(:baz, true)
29
+ scanner.scan(modules, true, false)
30
+ expect(scanner.candidates).to have_names(:baz, :baz)
31
+ end
32
+
33
+ it "completes private module methods" do
34
+ scanner = klass.new(:tee, true)
35
+ scanner.scan(modules, false, true)
36
+ expect(scanner.candidates).to have_names(:tee, :tee)
37
+ end
38
+
39
+ it "completes nothing when shouldn't" do
40
+ scanner = klass.new(:foo, true)
41
+ scanner.scan(modules, false, false)
42
+ expect(scanner.candidates).to be_empty
43
+ end
44
+
45
+ RSpec::Matchers.define :have_names do |*names|
46
+ match do |candidates|
47
+ candidates.map(&:name) == names
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'robe/scanners'
3
+
4
+ describe Robe::ModuleScanner do
5
+ include ScannerHelper
6
+
7
+ klass = described_class
8
+
9
+ let(:a) { new_module(:foo, :bar, :baz, :tee) }
10
+ let(:b) { new_module(:bar, :foo, :baz, :tee) }
11
+ let(:c) { new_module(:foo, :bar, :tee, :baz) }
12
+ let(:d) { new_module(:bar, :foo, :tee, :baz) }
13
+ let(:e) { Module.new }
14
+ let(:modules) { [a, b, c, d, e ] }
15
+
16
+ it "finds instance methods" do
17
+ scanner = klass.new(:foo, false)
18
+ scanner.scan(modules, true, false)
19
+ expect(scanner.candidates).to eq [a, c].map { |s| s.instance_method(:foo) }
20
+ end
21
+
22
+ it "finds module methods" do
23
+ scanner = klass.new(:foo, false)
24
+ scanner.scan(modules, false, true)
25
+ expect(scanner.candidates).to eq [b, d].map { |s| s.method(:foo).unbind }
26
+ end
27
+
28
+ it "find private instance methods" do
29
+ scanner = klass.new(:baz, true)
30
+ scanner.scan(modules, true, false)
31
+ expect(scanner.candidates).to eq [a, b].map { |s| s.instance_method(:baz) }
32
+ end
33
+
34
+ it "finds private module methods" do
35
+ scanner = klass.new(:tee, true)
36
+ scanner.scan(modules, false, true)
37
+ expect(scanner.candidates).to eq [a, b].map { |s| s.method(:tee).unbind }
38
+ end
39
+
40
+ it "finds nothing when shouldn't" do
41
+ scanner = klass.new(:foo, true)
42
+ scanner.scan(modules, false, false)
43
+ expect(scanner.candidates).to eq []
44
+ end
45
+
46
+ it "finds both types when should" do
47
+ scanner = klass.new(:foo, false)
48
+ scanner.scan(modules, true, true)
49
+ expect(scanner.candidates).to eq([a.instance_method(:foo),
50
+ b.method(:foo).unbind, c.instance_method(:foo), d.method(:foo).unbind])
51
+ end
52
+
53
+ it "doesn't include methods from Module in module methods" do
54
+ scanner = klass.new(:attr, true)
55
+ scanner.scan([Module.new], true, true)
56
+ expect(scanner.candidates).to be_empty
57
+ end
58
+
59
+ it "doesn't pay attention to overridden [private_]instance_methods" do
60
+ tiff = new_module(:foo, :bar, :bar, :foo)
61
+
62
+ tiff = Module.new do
63
+ def self.instance_methods(_ign)
64
+ [:fool, :me, :once]
65
+ end
66
+
67
+ def self.private_instance_methods(_ign)
68
+ [:fool, :me, :twice]
69
+ end
70
+ end
71
+
72
+ scanner = klass.new(:fool, true)
73
+ scanner.scan([tiff], true, true)
74
+ expect(scanner.candidates).to be_empty
75
+ end
76
+ end
@@ -0,0 +1,165 @@
1
+ require 'robe/sash/doc_for'
2
+ require 'uri'
3
+ require 'active_support/core_ext/kernel' # https://github.com/pry/pry-doc/issues/16
4
+
5
+ describe Robe::Sash::DocFor do
6
+ klass = described_class
7
+
8
+ it "returns hash for a basic class" do
9
+ c = Class.new do
10
+ # Some words.
11
+ def quux(a, *b, &c); end
12
+ end
13
+
14
+ k = klass.new(c.instance_method(:quux))
15
+ expect(k.format).to eq({docstring: "Some words.\n",
16
+ source: "def quux(a, *b, &c); end\n",
17
+ aliases: [],
18
+ visibility: :public})
19
+ end
20
+
21
+ it "shows docs for built-in classes" do
22
+ hash = klass.new(Hash.instance_method(:update)).format
23
+ expect(hash[:aliases]).to eq([:merge!])
24
+ if RUBY_ENGINE == "ruby"
25
+ expect(hash[:docstring]).to include("Adds the contents")
26
+ expect(hash[:source]).to include("rb_hash_foreach")
27
+ else
28
+ expect(hash[:docstring]).to eq("")
29
+ expect(hash[:source]).to start_with("def merge!(other)\n")
30
+ end
31
+ end
32
+
33
+ it "shows docs for stdlib classes" do
34
+ hash = klass.new(URI.method(:parse)).format
35
+ expect(hash[:docstring]).to include("one of the URI's subclasses")
36
+ end
37
+
38
+ it "returns private visibility for Kernel#puts" do
39
+ expect(klass.new(Kernel.instance_method(:puts)).format[:visibility])
40
+ .to eq(:private)
41
+ end
42
+
43
+ it "returns public visibility for Kernel.puts" do
44
+ # And doesn't do "Scanning and caching *.c files".
45
+ expect(klass.new(Kernel.method(:puts)).format[:visibility]).to eq(:public)
46
+ end
47
+
48
+ it "returns protected visibility" do
49
+ method = Class.new.class_exec do
50
+ protected
51
+ def foo; end
52
+ instance_method(:foo)
53
+ end
54
+ expect(klass.new(method).format[:visibility]).to be(:protected)
55
+ end
56
+
57
+ it "mentions pry-doc when relevant" do
58
+ val = Pry.config.has_pry_doc
59
+ Pry.config.has_pry_doc = false
60
+ struct = described_class.method_struct(String.instance_method(:gsub))
61
+ Pry.config.has_pry_doc = val
62
+
63
+ if RUBY_ENGINE == "ruby"
64
+ expect(struct.source).to be_nil
65
+ expect(struct.docstring).to match(/pry-doc/)
66
+ else
67
+ expect(struct.docstring).to eq("")
68
+ expect(struct.source).to start_with("def gsub(")
69
+ end
70
+ end
71
+
72
+ context "native methods" do
73
+ let(:c) { described_class }
74
+
75
+ context "String#gsub info fields" do
76
+ let(:struct) { c.method_struct(String.instance_method(:gsub)) }
77
+
78
+ if RUBY_ENGINE == "ruby"
79
+ it { expect(struct.docstring).to start_with("Returns")}
80
+ it { expect(struct.source).to start_with("static VALUE") }
81
+ else
82
+ it { expect(struct.docstring).to eq("") }
83
+ it { expect(struct.source).to start_with("def gsub(") }
84
+ end
85
+ end
86
+
87
+ context "Array#map has an alias" do
88
+ let(:struct) { c.method_struct(Array.instance_method(:map)) }
89
+ it { expect(struct.aliases).to eq([:collect])}
90
+ end
91
+
92
+ context "Set#include? has an alias" do
93
+ let(:struct) { c.method_struct(Set.instance_method(:include?)) }
94
+ it { expect(struct.aliases).to eq([:member?])}
95
+ end
96
+
97
+ context "Know the appropriate amount about Kernel#is_a?" do
98
+ let(:struct) { c.method_struct(Kernel.instance_method(:is_a?)) }
99
+
100
+ it { expect(struct.visibility).to be_nil }
101
+
102
+ if RUBY_ENGINE == "ruby"
103
+ it { expect(struct.docstring).to include("one of the superclasses") }
104
+ it { expect(struct.aliases).to eq([:kind_of?]) }
105
+ it { expect(struct.source).to start_with("VALUE\nrb_obj_is_kind_of") }
106
+ else
107
+ it { expect(struct.docstring).to include("class or superclass") }
108
+ it { expect(struct.aliases).to eq([:kind_of?]) }
109
+ it { expect(struct.source).to start_with("def kind_of?(") }
110
+ end
111
+ end
112
+ end
113
+
114
+ context "pure methods" do
115
+ let(:c) { described_class }
116
+
117
+ context "method quux defined" do
118
+ # First line,
119
+ # second line.
120
+ def quux(n)
121
+ end
122
+
123
+ it "has the docstring" do
124
+ expect(c.method_struct(method(:quux)).docstring).to eq("First line,\n" +
125
+ "second line.\n")
126
+ end
127
+
128
+ it "has the source" do
129
+ expect(c.method_struct(method(:quux)).source).to eq("def quux(n)\nend\n")
130
+ end
131
+
132
+ it "has no aliases" do
133
+ expect(c.method_struct(method(:quux)).aliases).to eq([])
134
+ end
135
+ end
136
+
137
+ it "should return the source for one-line methods" do
138
+ def xuuq(); end
139
+ expect(c.method_struct(method(:xuuq)).source).to eq("def xuuq(); end\n")
140
+ end
141
+
142
+ it "should return empty docstring when none" do
143
+ def xuuq(m)
144
+ end
145
+
146
+ struct = c.method_struct(method(:xuuq))
147
+ expect(struct.docstring).to eq("")
148
+ expect(struct.source).not_to be_empty
149
+ end
150
+ end
151
+
152
+ context "dynamically defined" do
153
+ let(:kls) { eval "Class.new do;def foo;42;end;end" }
154
+ let(:c) { described_class }
155
+
156
+ it "returns no docstring" do
157
+ expect(c.method_struct(kls.instance_method(:foo)).docstring).to be_empty
158
+ end
159
+
160
+ it "returns comment about dynamic definition as the source" do
161
+ expect(c.method_struct(kls.instance_method(:foo)).source)
162
+ .to include("outside of a source file")
163
+ end
164
+ end
165
+ end